| Index: frog/tip/tip.dart
 | 
| diff --git a/frog/tip/tip.dart b/frog/tip/tip.dart
 | 
| deleted file mode 100644
 | 
| index ed72932cba25aa0076cd00836157e06d2e9b9de5..0000000000000000000000000000000000000000
 | 
| --- a/frog/tip/tip.dart
 | 
| +++ /dev/null
 | 
| @@ -1,1624 +0,0 @@
 | 
| -// Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
 | 
| -// for details. All rights reserved. Use of this source code is governed by a
 | 
| -// BSD-style license that can be found in the LICENSE file.
 | 
| -
 | 
| -/**
 | 
| - * This giant file has pieces for compiling dart in the browser, injecting
 | 
| - * scripts into pages and a bunch of classes to build a simple dart editor.
 | 
| - *
 | 
| - * TODO(jimhug): Separate these pieces cleanly.
 | 
| - */
 | 
| -
 | 
| -#import('dart:dom');
 | 
| -#import('../lang.dart');
 | 
| -#import('../file_system_dom.dart');
 | 
| -
 | 
| -
 | 
| -void main() {
 | 
| -  final systemPath = getRootPath(window) + '/..';
 | 
| -  final userPath = getRootPath(window.parent);
 | 
| -
 | 
| -  if (window !== window.parent) {
 | 
| -    // I'm in an iframe - frogify my surrounding code unless dart is native.
 | 
| -    if (!document.implementation.hasFeature('dart', '')) {
 | 
| -      // Suppress warnings to avoid diff-based tests from failing on them.
 | 
| -      initialize(systemPath, userPath, ['--suppress_warnings']);
 | 
| -      window.addEventListener('DOMContentLoaded',
 | 
| -                              (e) => frogify(window.parent),
 | 
| -                              false);
 | 
| -    }
 | 
| -  } else {
 | 
| -    // I'm at the top level - run the tip shell.
 | 
| -    shell = new Shell();
 | 
| -    document.body.appendChild(shell._node);
 | 
| -    initialize(systemPath, userPath);
 | 
| -    world.messageHandler = shell.handleMessage;
 | 
| -  }
 | 
| -}
 | 
| -
 | 
| -
 | 
| -/** The filename to use for Dart code compiled directly from the browser. */
 | 
| -final String DART_FILENAME = 'input.dart';
 | 
| -
 | 
| -/** Helper to get my base url. */
 | 
| -String getRootPath(Window window) {
 | 
| -  String url = window.location.href;
 | 
| -  final tail = url.lastIndexOf('/', url.length);
 | 
| -  final dir = url.substring(0, tail);
 | 
| -  return dir;
 | 
| -}
 | 
| -
 | 
| -
 | 
| -/**
 | 
| - * Invoke this script in the given window's context.  Use
 | 
| - * this frame's window by default.
 | 
| - */
 | 
| -void inject(String code, Window win, [String name = 'generated.js']) {
 | 
| -  final doc = win.document;
 | 
| -  var script = doc.createElement('script');
 | 
| -  // TODO(vsm): Enable debugging of injected code.  This sourceURL
 | 
| -  // trick only appears to work for eval'ed code, not script injected
 | 
| -  // code.
 | 
| -  // Append sourceURL to enable debugging.
 | 
| -  script.innerHTML = code + '\n//@ sourceURL=$name';
 | 
| -  script.type = 'application/javascript';
 | 
| -  doc.body.appendChild(script);
 | 
| -}
 | 
| -
 | 
| -
 | 
| -/**
 | 
| - * Compile all dart scripts in a window and inject/invoke the corresponding JS.
 | 
| - */
 | 
| -void frogify(Window win) {
 | 
| -  var doc = win.document;
 | 
| -  int n = doc.scripts.length;
 | 
| -  // TODO(vsm): Implement foreach iteration on native DOM types.  This
 | 
| -  // should be for (var script in doc.scripts) { ... }.
 | 
| -  for (int i = 0; i < n; ++i) {
 | 
| -    final script = doc.scripts[i];
 | 
| -    if (script.type == 'application/dart') {
 | 
| -      final src = script.src;
 | 
| -      var input;
 | 
| -      var name;
 | 
| -      if (src == '') {
 | 
| -        input = script.innerHTML;
 | 
| -        name = null;
 | 
| -      } else {
 | 
| -        input = world.files.readAll(src);
 | 
| -        name = '$src.js';
 | 
| -      }
 | 
| -      world.files.writeString(DART_FILENAME, input);
 | 
| -      world.reset();
 | 
| -      var success = world.compile();
 | 
| -
 | 
| -      if (success) {
 | 
| -        inject(world.getGeneratedCode(), win, name);
 | 
| -      } else {
 | 
| -        inject('window.alert("compilation failed");', win, name);
 | 
| -      }
 | 
| -    }
 | 
| -  }
 | 
| -}
 | 
| -
 | 
| -void initialize(String systemPath, String userPath,
 | 
| -    [List<String> flags = const []]) {
 | 
| -  DomFileSystem fs = new DomFileSystem(userPath);
 | 
| -  // TODO(jimhug): Workaround lib path hack in frog_options.dart
 | 
| -  final options = [null, null, '--libdir=$systemPath/lib'];
 | 
| -  options.addAll(flags);
 | 
| -  options.add(DART_FILENAME);
 | 
| -  parseOptions(systemPath, options, fs);
 | 
| -  initializeWorld(fs);
 | 
| -}
 | 
| -
 | 
| -final int LINE_HEIGHT = 22; // TODO(jimhug): This constant sucks.
 | 
| -final int CHAR_WIDTH = 8; // TODO(jimhug): See above.
 | 
| -
 | 
| -final String CODE = '''
 | 
| -#import("dart:dom");
 | 
| -
 | 
| -// This is an interesting field;
 | 
| -final int y = 22;
 | 
| -String name;
 | 
| -
 | 
| -/** This is my main method. */
 | 
| -void main() {
 | 
| -  var element = document.createElement('div');
 | 
| -  element.innerHTML = "Hello dom from Dart!";
 | 
| -  document.body.appendChild(element);
 | 
| -
 | 
| -  HTMLCanvasElement canvas = document.createElement('canvas');
 | 
| -  document.body.appendChild(canvas);
 | 
| -
 | 
| -  var context = canvas.getContext('2d');
 | 
| -  context.setFillColor('purple');
 | 
| -  context.fillRect(10, 10, 30, 30);
 | 
| -}
 | 
| -
 | 
| -/**
 | 
| - * The usual method of computing factorial in the slowest possible way.
 | 
| - */
 | 
| -num fact(n) {
 | 
| -  if (n == 0) return 1;
 | 
| -  return n * fact(n - 1);
 | 
| -}
 | 
| -
 | 
| -final x = 22;
 | 
| -
 | 
| -''';
 | 
| -
 | 
| -final String HCODE = '''#import("dart:html");
 | 
| -
 | 
| -void main() {
 | 
| -  var element = document.createElement('div');
 | 
| -  element.innerHTML = "Hello html from Dart!";
 | 
| -  document.body.nodes.add(element);
 | 
| -
 | 
| -  CanvasElement canvas = document.createElement('canvas');
 | 
| -  canvas.width = 100;
 | 
| -  canvas.height = 100;
 | 
| -  document.body.nodes.add(canvas);
 | 
| -
 | 
| -  var context = canvas.getContext('2d');
 | 
| -  context.setFillColor('blue');
 | 
| -  context.fillRect(10, 10, 30, 30);
 | 
| -}
 | 
| -''';
 | 
| -
 | 
| -
 | 
| -class Message {
 | 
| -  var _node;
 | 
| -  String prefix, message;
 | 
| -  SourceSpan span;
 | 
| -
 | 
| -  Message(this.prefix, this.message, this.span) {
 | 
| -    var col = prefix.indexOf(':');
 | 
| -    if (col != -1) {
 | 
| -      prefix = prefix.substring(0, col);
 | 
| -    }
 | 
| -
 | 
| -    _node = document.createElement('div');
 | 
| -    _node.className = 'message ${prefix}';
 | 
| -    _node.innerText = '$message at ${span.locationText}';
 | 
| -
 | 
| -    _node.addEventListener('click', click, false);
 | 
| -  }
 | 
| -
 | 
| -  void click(MouseEvent event) {
 | 
| -    shell.showSpan(span);
 | 
| -  }
 | 
| -}
 | 
| -
 | 
| -class MessageWindow {
 | 
| -  var _node;
 | 
| -  List<Message> messages;
 | 
| -
 | 
| -  MessageWindow() {
 | 
| -    messages = [];
 | 
| -    _node = document.createElement('div');
 | 
| -    _node.className = 'errors';
 | 
| -  }
 | 
| -
 | 
| -  addMessage(Message message) {
 | 
| -    messages.add(message);
 | 
| -    _node.appendChild(message._node);
 | 
| -  }
 | 
| -
 | 
| -  clear() {
 | 
| -    messages.length = 0;
 | 
| -    _node.innerHTML = '';
 | 
| -  }
 | 
| -}
 | 
| -
 | 
| -
 | 
| -// TODO(jimhug): Type is needed.
 | 
| -Shell shell;
 | 
| -
 | 
| -class Shell {
 | 
| -  var _textInputArea;
 | 
| -  KeyBindings _bindings;
 | 
| -
 | 
| -  Cursor cursor;
 | 
| -  Editor _editor;
 | 
| -
 | 
| -  var _node;
 | 
| -  var _output;
 | 
| -
 | 
| -  var _repl;
 | 
| -  var _errors;
 | 
| -
 | 
| -  Shell() {
 | 
| -    _node = document.createElement('div');
 | 
| -    _node.className = 'shell';
 | 
| -    _editor = new Editor(this);
 | 
| -
 | 
| -    _editor._node.style.setProperty('height', '93%');
 | 
| -    _node.appendChild(_editor._node);
 | 
| -
 | 
| -    _textInputArea = document.createElement('textarea');
 | 
| -    _textInputArea.className = 'hiddenTextArea';
 | 
| -
 | 
| -    _node.appendChild(_textInputArea);
 | 
| -
 | 
| -    var outDiv = document.createElement('div');
 | 
| -    outDiv.className = 'output';
 | 
| -    outDiv.style.setProperty('height', '83%');
 | 
| -
 | 
| -
 | 
| -    _output = document.createElement('iframe');
 | 
| -    outDiv.appendChild(_output);
 | 
| -    _node.appendChild(outDiv);
 | 
| -
 | 
| -    _repl = document.createElement('div');
 | 
| -    _repl.className = 'repl';
 | 
| -    _repl.style.setProperty('height', '5%');
 | 
| -    _repl.innerHTML = '<div>REPL Under Construction...</div>';
 | 
| -    _node.appendChild(_repl);
 | 
| -
 | 
| -    _errors = new MessageWindow();
 | 
| -
 | 
| -    //_errors.innerHTML = '<h3>Errors/Warnings Under Construction...</h3>';
 | 
| -    _errors._node.style.setProperty('height', '15%');
 | 
| -    _node.appendChild(_errors._node);
 | 
| -
 | 
| -    // TODO(jimhug): Ick!
 | 
| -    window.setTimeout( () {
 | 
| -      _editor.focus();
 | 
| -      _output.contentDocument.head.innerHTML = '''
 | 
| -        <style>body {
 | 
| -          font-family: arial , sans-serif;
 | 
| -        }
 | 
| -        h3 {
 | 
| -          text-align: center;
 | 
| -        }
 | 
| -        </style>''';
 | 
| -      _output.contentDocument.body.innerHTML = '<h3>Output will go here</h3>';
 | 
| -    }, .5);
 | 
| -
 | 
| -    // TODO(jimhug): These are hugely incomplete and Mac-centric.
 | 
| -    var bindings = {
 | 
| -      'Left': () {
 | 
| -        cursor.clearSelection();
 | 
| -        cursor._pos = cursor._pos.moveColumn(-1);
 | 
| -      },
 | 
| -      'Shift-Left': () {
 | 
| -        if (cursor._toPos == null) {
 | 
| -          cursor._toPos = cursor._pos;
 | 
| -        }
 | 
| -        cursor._pos = cursor._pos.moveColumn(-1);
 | 
| -      },
 | 
| -
 | 
| -      'Right': () {
 | 
| -        cursor.clearSelection();
 | 
| -        cursor._pos = cursor._pos.moveColumn(+1);
 | 
| -      },
 | 
| -      'Shift-Right': () {
 | 
| -        if (cursor._toPos == null) {
 | 
| -          cursor._toPos = cursor._pos;
 | 
| -        }
 | 
| -        cursor._pos = cursor._pos.moveColumn(+1);
 | 
| -      },
 | 
| -      'Up': () {
 | 
| -        cursor.clearSelection();
 | 
| -        // TODO(jimhug): up and down lose column info on shorter lines.
 | 
| -        cursor._pos = cursor._pos.moveLine(-1);
 | 
| -      },
 | 
| -      'Shift-Up': () {
 | 
| -        if (cursor._toPos == null) {
 | 
| -          cursor._toPos = cursor._pos;
 | 
| -        }
 | 
| -        cursor._pos = cursor._pos.moveLine(-1);
 | 
| -      },
 | 
| -      'Down': () {
 | 
| -        cursor.clearSelection();
 | 
| -        cursor._pos = cursor._pos.moveLine(+1);
 | 
| -      },
 | 
| -      'Shift-Down': () {
 | 
| -        if (cursor._toPos == null) {
 | 
| -          cursor._toPos = cursor._pos;
 | 
| -        }
 | 
| -        cursor._pos = cursor._pos.moveLine(+1);
 | 
| -      },
 | 
| -
 | 
| -      'Meta-Up': () {
 | 
| -        cursor.clearSelection();
 | 
| -        cursor._pos = _editor._code.start;
 | 
| -      },
 | 
| -      'Meta-Shift-Up': () {
 | 
| -        if (cursor._toPos == null) {
 | 
| -          cursor._toPos = cursor._pos;
 | 
| -        }
 | 
| -        cursor._pos = _editor._code.start;
 | 
| -      },
 | 
| -
 | 
| -      'Meta-Down': () {
 | 
| -        cursor.clearSelection();
 | 
| -        cursor._pos = _editor._code.end;
 | 
| -      },
 | 
| -      'Meta-Shift-Down': () {
 | 
| -        if (cursor._toPos == null) {
 | 
| -          cursor._toPos = cursor._pos;
 | 
| -        }
 | 
| -        cursor._pos = _editor._code.end;
 | 
| -      },
 | 
| -
 | 
| -      'Delete': () {
 | 
| -        //TODO(jimhug): go back to beginning of line when appropriate.
 | 
| -        if (cursor._toPos == null) {
 | 
| -          cursor._toPos = cursor._pos.moveColumn(-1);
 | 
| -        }
 | 
| -        cursor.deleteSelection();
 | 
| -      },
 | 
| -      'Control-D': () {
 | 
| -        if (cursor._toPos == null) {
 | 
| -          cursor._toPos = cursor._pos.moveColumn(+1);
 | 
| -        }
 | 
| -        cursor.deleteSelection();
 | 
| -      },
 | 
| -
 | 
| -      'Meta-A': () {
 | 
| -        cursor._pos = _editor._code.start;
 | 
| -        cursor._toPos = _editor._code.end;
 | 
| -      },
 | 
| -
 | 
| -      '.' : () {
 | 
| -        // TODO(jimhug): complete hints here
 | 
| -        cursor.write('.');
 | 
| -      },
 | 
| -
 | 
| -      '}' : () {
 | 
| -        // TODO(jimhug): Get indentation right.
 | 
| -        cursor.write('}');
 | 
| -      },
 | 
| -
 | 
| -
 | 
| -      'Enter': () {
 | 
| -        cursor.write('\n');
 | 
| -        // TODO(jimhug): Indent on the new line appropriately...
 | 
| -      },
 | 
| -
 | 
| -      'Tab': () {
 | 
| -        // TODO(jimhug): force tab to always "properly" indent the line
 | 
| -        cursor.write('  ');
 | 
| -      },
 | 
| -
 | 
| -      'Space': () => cursor.write(' '),
 | 
| -      // This seems to be a common typo, so just allow it.
 | 
| -      'Shift-Space': () => cursor.write(' '),
 | 
| -
 | 
| -      'Meta-G': () => cursor.write(CODE),
 | 
| -      'Meta-H': () => cursor.write(HCODE),
 | 
| -
 | 
| -      'Meta-P': () => cursor._pos.block.parse(),
 | 
| -
 | 
| -      'Shift-Enter': run,
 | 
| -      'Meta-Enter': run,
 | 
| -    };
 | 
| -
 | 
| -    _bindings = new KeyBindings(_textInputArea, bindings,
 | 
| -        (String text) { cursor.write(text); },
 | 
| -        (String key) {
 | 
| -          // Use native bindings for cut and paste
 | 
| -          if (key == 'Meta-V') return false;
 | 
| -          if (key == 'Control-V') return false;
 | 
| -
 | 
| -          // TODO(jimhug): No good, very bad hack.
 | 
| -          if (key == 'Meta-X' || key == 'Control-X') {
 | 
| -            window.setTimeout(() {
 | 
| -              cursor.deleteSelection();
 | 
| -              _editor._redraw();
 | 
| -            }, 0);
 | 
| -            return false;
 | 
| -          }
 | 
| -
 | 
| -          if (key == 'Meta-C') return false;
 | 
| -          if (key == 'Control-C') return false;
 | 
| -
 | 
| -          // Use native bindings for dev tools
 | 
| -          if (key == 'Alt-Meta-I') return false;
 | 
| -          if (key == 'Control-Shift-I') return false;
 | 
| -          window.console.log('Unbound key "$key"');
 | 
| -          return true;
 | 
| -        });
 | 
| -  }
 | 
| -
 | 
| -  void handleMessage(String prefix, String message, SourceSpan span) {
 | 
| -    var m = new Message(prefix, message, span);
 | 
| -    _errors.addMessage(m);
 | 
| -  }
 | 
| -
 | 
| -  void showSpan(SourceSpan span) {
 | 
| -    // TODO(jimhug): Get code from correct file.
 | 
| -    var code = _editor._code;
 | 
| -    var p1 = new CodePosition(code, span.start);
 | 
| -    var p2 = new CodePosition(code, span.end);
 | 
| -    _editor._cursor._pos = p1.toLeaf();
 | 
| -    _editor._cursor._toPos = p2.toLeaf();
 | 
| -    _editor._redraw();
 | 
| -  }
 | 
| -
 | 
| -  void run() {
 | 
| -    _output.contentDocument.body.innerHTML =
 | 
| -      '<h3 style="color:green">Compiling...</h3>';
 | 
| -    _errors.clear();
 | 
| -
 | 
| -    window.setTimeout( () {
 | 
| -      final sw = new Stopwatch();
 | 
| -      sw.start();
 | 
| -      var code = _editor.getCode();
 | 
| -      world.files.writeString(DART_FILENAME, code);
 | 
| -      options.enableAsserts = true;
 | 
| -      options.enableTypeChecks = true;
 | 
| -      world.reset();
 | 
| -      var success = world.compile();
 | 
| -      sw.stop();
 | 
| -
 | 
| -      if (success) {
 | 
| -        _output.contentDocument.body.innerHTML = '';
 | 
| -        inject(world.getGeneratedCode(), _output.contentWindow);
 | 
| -      } else {
 | 
| -        _output.contentDocument.body.innerHTML =
 | 
| -          '<h3 style="color:red">Compilation failed</h3>';
 | 
| -      }
 | 
| -
 | 
| -      print('compiled in ${sw.elapsedInMs()}msec');
 | 
| -    }, 0);
 | 
| -  }
 | 
| -
 | 
| -  void focusKeys(Editor editor) {
 | 
| -    _editor = editor;
 | 
| -    cursor = editor._cursor;
 | 
| -    _textInputArea.focus();
 | 
| -  }
 | 
| -}
 | 
| -
 | 
| -
 | 
| -class Editor {
 | 
| -  Shell _shell;
 | 
| -  Cursor _cursor;
 | 
| -  CodeBlock _code;
 | 
| -  var _node;
 | 
| -
 | 
| -  // Some temp state for mouse down and selections
 | 
| -  bool _isSelecting;
 | 
| -  int _lastClickTime;
 | 
| -  bool _didDoubleClick;
 | 
| -
 | 
| -  Editor(this._shell) {
 | 
| -    _node = document.createElement('div');
 | 
| -    _node.className = 'editor';
 | 
| -
 | 
| -    _code = new BlockBlock(null, 0);
 | 
| -    _code.text = CODE;
 | 
| -    _code.top = 0;
 | 
| -    _node.appendChild(_code._node);
 | 
| -
 | 
| -    _cursor = new Cursor(_code.start);
 | 
| -    _node.appendChild(_cursor._node);
 | 
| -
 | 
| -    _node.addEventListener('mousedown', mousedown, false);
 | 
| -    _node.addEventListener('mousemove', mousemove, false);
 | 
| -    _node.addEventListener('mouseup', mouseup, false);
 | 
| -
 | 
| -    // TODO(jimhug): Final bit to make region selection clean.
 | 
| -  	//this.node.addEventListener('mouseout', this, false);
 | 
| -
 | 
| -    // TODO(jimhug): Lazy rendering should be triggered by this.
 | 
| -    //_node.addEventListener('scroll', this, false);
 | 
| -  }
 | 
| -
 | 
| -  String getCode() {
 | 
| -    return _code.text;
 | 
| -  }
 | 
| -
 | 
| -  void goto(int line, int column) {
 | 
| -    _cursor._pos = _code.getPosition(line, column);
 | 
| -  }
 | 
| -
 | 
| -  void mousedown(MouseEvent e) {
 | 
| -    // for shift click, create selection region
 | 
| -    if (e.shiftKey) {
 | 
| -      _cursor._toPos = _cursor._pos;
 | 
| -    } else {
 | 
| -      _cursor.clearSelection();
 | 
| -    }
 | 
| -    _cursor._pos = _code.positionFromMouse(e);
 | 
| -    focus();
 | 
| -    e.preventDefault();
 | 
| -
 | 
| -    _isSelecting = true;
 | 
| -  }
 | 
| -
 | 
| -  void mousemove(MouseEvent e) {
 | 
| -    // TODO(jimhug): Would REALLY like to check that button is down here!
 | 
| -    if (_isSelecting) {
 | 
| -      if (_cursor._toPos == null) {
 | 
| -        _cursor._toPos = _cursor._pos;
 | 
| -      }
 | 
| -      _cursor._pos = _code.positionFromMouse(e);
 | 
| -      e.preventDefault();
 | 
| -      _redraw();
 | 
| -    }
 | 
| -  }
 | 
| -
 | 
| -  void mouseup(MouseEvent e) {
 | 
| -    _isSelecting = false;
 | 
| -    if (_cursor.emptySelection) {
 | 
| -      _cursor.clearSelection();
 | 
| -    }
 | 
| -  }
 | 
| -
 | 
| -  void scrollToVisible(CodePosition pos) {
 | 
| -    var top = _node.scrollTop;
 | 
| -    var height = _node.getBoundingClientRect().height;
 | 
| -    var pt = pos.getPoint();
 | 
| -
 | 
| -    if (pt.y < top) {
 | 
| -      _node.scrollTop = pt.y;
 | 
| -    } else if (pt.y > top + height - LINE_HEIGHT) {
 | 
| -      var H = LINE_HEIGHT * ((height ~/ LINE_HEIGHT) - 1);
 | 
| -      _node.scrollTop = Math.max(pt.y - H, 0);
 | 
| -    }
 | 
| -   }
 | 
| -
 | 
| -  void _redraw() {
 | 
| -    scrollToVisible(_cursor._pos);
 | 
| -    _code._redraw();
 | 
| -    _cursor._redraw();
 | 
| -  }
 | 
| -
 | 
| -  void focus() {
 | 
| -    _shell.focusKeys(this);
 | 
| -    _cursor._visible = true;
 | 
| -    _redraw();
 | 
| -  }
 | 
| -
 | 
| -  void blur() {
 | 
| -    _shell.blurKeys(this);
 | 
| -    _cursor._visible = false;
 | 
| -    _redraw();
 | 
| -  }
 | 
| -}
 | 
| -
 | 
| -class Point {
 | 
| -  final int x, y;
 | 
| -  const Point(this.x, this.y);
 | 
| -}
 | 
| -
 | 
| -class LineColumn {
 | 
| -  final int line, column;
 | 
| -  LineColumn(this.line, this.column);
 | 
| -}
 | 
| -
 | 
| -class Cursor {
 | 
| -  CodePosition _pos;
 | 
| -
 | 
| -  CodePosition _toPos;
 | 
| -  bool _visible = true;
 | 
| -  var _node;
 | 
| -
 | 
| -  var _cursorNode;
 | 
| -  var _selectionNode;
 | 
| -
 | 
| -  Cursor(this._pos) {
 | 
| -    _node = document.createElement('div');
 | 
| -    _node.className = 'cursorDiv';
 | 
| -  }
 | 
| -
 | 
| -  bool get emptySelection() {
 | 
| -    return _toPos == null ||
 | 
| -      (_toPos.block == _pos.block && _toPos.offset == _pos.offset);
 | 
| -  }
 | 
| -
 | 
| -  _redraw() {
 | 
| -    // Approach is to kill and recreate everything on a redraw.
 | 
| -    // There are lots of potential improvements if this proves costly.
 | 
| -    // However: If we don't do this we need a different dance to make cursor
 | 
| -    // blinking disabled when it is moving.
 | 
| -
 | 
| -    _node.innerHTML = '';
 | 
| -    if (!_visible) return;
 | 
| -
 | 
| -    _cursorNode = document.createElement('div');
 | 
| -    _cursorNode.className = 'cursor blink';
 | 
| -    _cursorNode.style.setProperty('height', '${LINE_HEIGHT}px');
 | 
| -
 | 
| -    var p = _pos.getPoint();
 | 
| -    _cursorNode.style.setProperty('left', '${p.x}px');
 | 
| -    _cursorNode.style.setProperty('top', '${p.y}px');
 | 
| -    _node.appendChild(_cursorNode);
 | 
| -
 | 
| -    if (_toPos == null) return;
 | 
| -
 | 
| -    void addDiv(top, left, height, width) {
 | 
| -      var child = document.createElement('div');
 | 
| -      child.className = 'selection';
 | 
| -      child.style.setProperty('left', '${left}px');
 | 
| -      child.style.setProperty('top', '${top}px');
 | 
| -
 | 
| -      child.style.setProperty('height', '${height}px');
 | 
| -      if (width == null) {
 | 
| -        child.style.setProperty('right', '0px');
 | 
| -      } else {
 | 
| -        child.style.setProperty('width', '${width}px');
 | 
| -      }
 | 
| -      _node.appendChild(child);
 | 
| -    }
 | 
| -
 | 
| -    var toP = _toPos.getPoint();
 | 
| -    // Same line - only one line to highlight
 | 
| -    if (toP.y == p.y) {
 | 
| -      if (toP.x < p.x) {
 | 
| -        addDiv(p.y, toP.x, LINE_HEIGHT, p.x - toP.x);
 | 
| -      } else {
 | 
| -        addDiv(p.y, p.x, LINE_HEIGHT, toP.x - p.x);
 | 
| -      }
 | 
| -    } else {
 | 
| -      if (toP.y < p.y) {
 | 
| -        var tmp = toP; toP = p; p = tmp;
 | 
| -      }
 | 
| -      addDiv(p.y, p.x, LINE_HEIGHT, null);
 | 
| -      if (toP.y > p.y + LINE_HEIGHT) {
 | 
| -        addDiv(p.y + LINE_HEIGHT, 0, toP.y - p.y - LINE_HEIGHT, null);
 | 
| -      }
 | 
| -      addDiv(toP.y, 0, LINE_HEIGHT, toP.x);
 | 
| -    }
 | 
| -
 | 
| -    // TODO(jimhug): separate out - this makes default copy/cut work
 | 
| -    var p0 = _pos.toRoot();
 | 
| -    var p1 = _toPos.toRoot();
 | 
| -
 | 
| -    var i0 = p0.offset;
 | 
| -    var i1 = p1.offset;
 | 
| -    if (i1 < i0) {
 | 
| -      var tmp = i1; i1 = i0; i0 = tmp;
 | 
| -    }
 | 
| -    var text = p0.block.text.substring(i0, i1);
 | 
| -    shell._textInputArea.value = text;
 | 
| -    shell._textInputArea.select();
 | 
| -  }
 | 
| -
 | 
| -  void clearSelection() {
 | 
| -    _toPos = null;
 | 
| -  }
 | 
| -
 | 
| -  void moveColumn(int delta) {
 | 
| -    _pos = _pos.moveColumn(delta);
 | 
| -  }
 | 
| -
 | 
| -  void moveLine(int delta) {
 | 
| -    _pos = _pos.moveLine(delta);
 | 
| -  }
 | 
| -
 | 
| -  void deleteSelection() {
 | 
| -    if (_toPos == null) return;
 | 
| -
 | 
| -    var p0 = _pos;
 | 
| -    var p1 = _toPos;
 | 
| -
 | 
| -    if (p0.block != p1.block) {
 | 
| -      // move up to root and resolve there...
 | 
| -      p0 = p0.toRoot();
 | 
| -      p1 = p1.toRoot();
 | 
| -    }
 | 
| -    assert(p0.block == p1.block);
 | 
| -
 | 
| -    if (p1.offset < p0.offset) {
 | 
| -      var tmp = p0; p0 = p1; p1 = tmp;
 | 
| -    }
 | 
| -    p0.block.delete(p0.offset, p1.offset);
 | 
| -    _pos = p0.toLeaf();
 | 
| -    _toPos = null;
 | 
| -  }
 | 
| -
 | 
| -  void write(String text) {
 | 
| -    // TODO(jimhug): combine insert and delete to optimize
 | 
| -    deleteSelection();
 | 
| -    _pos.block.insertText(_pos.offset, text);
 | 
| -    _pos.block._redraw(); // TODO(jimhug): Egregious hack.
 | 
| -    _pos = _pos.moveColumn(text.length);
 | 
| -  }
 | 
| -}
 | 
| -
 | 
| -class CodePosition {
 | 
| -  final CodeBlock block;
 | 
| -  final int offset;
 | 
| -
 | 
| -  CodePosition(this.block, this.offset);
 | 
| -
 | 
| -  CodePosition moveLine(int delta) {
 | 
| -    if (delta == 0) return this;
 | 
| -
 | 
| -    var lineCol = getLineColumn();
 | 
| -    return block.getPosition(lineCol.line + delta, lineCol.column);
 | 
| -  }
 | 
| -
 | 
| -  CodePosition moveColumn(int delta) {
 | 
| -    return block.moveToOffset(offset + delta);
 | 
| -  }
 | 
| -
 | 
| -  Point getPoint() {
 | 
| -    return block.offsetToPoint(offset);
 | 
| -  }
 | 
| -
 | 
| -  LineColumn getLineColumn() {
 | 
| -    return block.getLineColumn(offset);
 | 
| -  }
 | 
| -
 | 
| -  CodePosition toRoot() {
 | 
| -    if (block._parent === null) return this;
 | 
| -
 | 
| -    var ret = new CodePosition(block._parent,
 | 
| -      block._parent.getOffset(block) + offset);
 | 
| -    return ret.toRoot();
 | 
| -  }
 | 
| -
 | 
| -  CodePosition toLeaf() {
 | 
| -    return block.moveToOffset(offset);
 | 
| -  }
 | 
| -}
 | 
| -
 | 
| -
 | 
| -/**
 | 
| - * Every [CodeBlock] must provide the following:
 | 
| - * - a node to render it - purely as vertical div
 | 
| - * - a height in real pixels and in code lines
 | 
| - * - proper interaction with CodePosition
 | 
| - * - accurate and working get/set for text property
 | 
| - * - appropriate integration into parsing
 | 
| - * - support to hold onto annotations of various sorts
 | 
| - */
 | 
| -class BlockChildren implements Iterable<CodeBlock> {
 | 
| -  BlockBlock _parent;
 | 
| -  BlockChildren(this._parent);
 | 
| -
 | 
| -  Iterator<CodeBlock> iterator() {
 | 
| -    return new BlockChildrenIterator(_parent._firstChild);
 | 
| -  }
 | 
| -}
 | 
| -
 | 
| -class BlockChildrenIterator implements Iterator<CodeBlock> {
 | 
| -  CodeBlock _child;
 | 
| -  BlockChildrenIterator(this._child);
 | 
| -
 | 
| -  // TODO(jimhug): current + moveNext() is much more sane.
 | 
| -  CodeBlock next() {
 | 
| -    var ret = _child;
 | 
| -    _child = _child._nextSibling;
 | 
| -    return ret;
 | 
| -  }
 | 
| -
 | 
| -  bool hasNext() => _child !== null;
 | 
| -}
 | 
| -
 | 
| -
 | 
| -/**
 | 
| - * Block block...  first and last children are special except for top file.
 | 
| - */
 | 
| -class BlockBlock extends CodeBlock {
 | 
| -  CodeBlock _firstChild;
 | 
| -  CodeBlock _lastChild;
 | 
| -
 | 
| -  Iterable<CodeBlock> children;
 | 
| -
 | 
| -  BlockBlock(CodeBlock parent, int depth): super(parent, depth) {
 | 
| -    text = '';
 | 
| -    children = new BlockChildren(this);
 | 
| -  }
 | 
| -
 | 
| -  CodePosition get start() => _firstChild.start;
 | 
| -
 | 
| -  CodePosition get end() => _lastChild.end;
 | 
| -
 | 
| -  int get size() {
 | 
| -    var ret = 0;
 | 
| -    for (var child in children) ret += child.size;
 | 
| -    return ret;
 | 
| -  }
 | 
| -
 | 
| -  int get lineCount() {
 | 
| -    var ret = 0;
 | 
| -    for (var child in children) ret += child.lineCount;
 | 
| -    return ret;
 | 
| -  }
 | 
| -
 | 
| -  // TODO(jimhug): This property is expensive here - should it be a prop?
 | 
| -  String get text() {
 | 
| -    var ret = new StringBuffer();
 | 
| -    for (var child in children) ret.add(child.text);
 | 
| -    return ret.toString();
 | 
| -  }
 | 
| -
 | 
| -  int getOffset(CodeBlock forChild) {
 | 
| -    var ret = 0;
 | 
| -    for (var child in children) {
 | 
| -      if (child == forChild) return ret;
 | 
| -      ret += child.size;
 | 
| -    }
 | 
| -    throw "child missing";
 | 
| -  }
 | 
| -
 | 
| -  int getLine(CodeBlock forChild) {
 | 
| -    var ret = 0;
 | 
| -    for (var child in children) {
 | 
| -      if (child == forChild) return ret;
 | 
| -      ret += child.lineCount;
 | 
| -    }
 | 
| -    throw "child missing";
 | 
| -  }
 | 
| -
 | 
| -  _addChildAfter(CodeBlock addAfterChild, CodeBlock child) {
 | 
| -    markDirty();
 | 
| -    child._nextSibling = addAfterChild._nextSibling;
 | 
| -    child._previousSibling = addAfterChild;
 | 
| -    addAfterChild._nextSibling = child;
 | 
| -
 | 
| -    // Add child's node into our DOM tree.
 | 
| -    if (child._nextSibling === null) {
 | 
| -      _node.appendChild(child._node);
 | 
| -      _lastChild = child;
 | 
| -    } else {
 | 
| -      _node.insertBefore(child._node, child._nextSibling._node);
 | 
| -    }
 | 
| -  }
 | 
| -
 | 
| -  _removeChild(CodeBlock child) {
 | 
| -    markDirty();
 | 
| -    if (child._previousSibling !== null) {
 | 
| -      child._previousSibling._nextSibling = child._nextSibling;
 | 
| -    } else {
 | 
| -      _firstChild = child._nextSibling;
 | 
| -    }
 | 
| -    if (child._nextSibling !== null) {
 | 
| -      child._nextSibling._previousSibling = child._previousSibling;
 | 
| -    } else {
 | 
| -      _lastChild = child._previousSibling;
 | 
| -    }
 | 
| -
 | 
| -    // Remove child's node from our DOM tree.
 | 
| -    _node.removeChild(child._node);
 | 
| -  }
 | 
| -
 | 
| -  void set text(String newText) {
 | 
| -    final sw = new Stopwatch();
 | 
| -    sw.start();
 | 
| -
 | 
| -    _firstChild = _lastChild = null;
 | 
| -
 | 
| -    final src = new SourceFile('fake.dart', newText);
 | 
| -    int start = 0;
 | 
| -    while (start != -1) {
 | 
| -      var child = new TextBlock(this, null, _depth + 1);
 | 
| -      if (_lastChild === null) {
 | 
| -        _firstChild = _lastChild = child;
 | 
| -        _node.appendChild(child._node);
 | 
| -      } else {
 | 
| -        _addChildAfter(_lastChild, child);
 | 
| -      }
 | 
| -      start = child.tokenizeInto(src, start);
 | 
| -    }
 | 
| -
 | 
| -    sw.stop();
 | 
| -
 | 
| -    print('create structure in ${sw.elapsedInMs()}msec');
 | 
| -  }
 | 
| -
 | 
| -  void insertText(int offset, String newText) {
 | 
| -    var index = 0;
 | 
| -    for (var child in children) {
 | 
| -      var childSize = child.size;
 | 
| -      if (offset < childSize) {
 | 
| -        child.insertText(offset, newText);
 | 
| -        return;
 | 
| -      } else if (offset == childSize) {
 | 
| -        // TODO(jimhug): Nasty merging of text and block structure here.
 | 
| -        var newChild = new TextBlock(this, newText, _depth + 1);
 | 
| -        _addChildAfter(child, newChild);
 | 
| -        return;
 | 
| -      }
 | 
| -      offset -= childSize;
 | 
| -      index++;
 | 
| -    }
 | 
| -    // TODO: nesting at this level
 | 
| -    throw "help";
 | 
| -  }
 | 
| -
 | 
| -  void delete(int from, int to) {
 | 
| -    assert(from <= to);
 | 
| -    var keepChild = null;
 | 
| -    for (var child = _firstChild; child !== null; child = child._nextSibling) {
 | 
| -      var childSize = child.size;
 | 
| -      if (keepChild !== null) {
 | 
| -        _removeChild(child);
 | 
| -        if (to <= childSize) {
 | 
| -          // abstraction violation!!!
 | 
| -          keepChild._text += child._text.substring(to);
 | 
| -          return;
 | 
| -        }
 | 
| -      } else if (from <= childSize) {
 | 
| -        if (to < childSize) {
 | 
| -          child.delete(from, to);
 | 
| -          return;
 | 
| -        } else {
 | 
| -          child.delete(from, childSize);
 | 
| -          keepChild = child;
 | 
| -        }
 | 
| -      }
 | 
| -      from -= childSize;
 | 
| -      to -= childSize;
 | 
| -    }
 | 
| -    // TODO: nesting at this level
 | 
| -    throw  "help";
 | 
| -  }
 | 
| -
 | 
| -  _redraw() {
 | 
| -    if (!_dirty) return;
 | 
| -    _dirty = false;
 | 
| -
 | 
| -    var childTop = 0;
 | 
| -    for (var child = _firstChild; child !== null; child = child._nextSibling) {
 | 
| -      // Note: Performance here relies on lazy setter in CodeBlock.
 | 
| -      child.top = childTop;
 | 
| -      child._redraw();
 | 
| -      childTop += child.height;
 | 
| -    }
 | 
| -
 | 
| -    height = childTop;
 | 
| -  }
 | 
| -
 | 
| -  CodePosition moveToOffset(int offset) {
 | 
| -    for (var child in children) {
 | 
| -      var childSize = child.size;
 | 
| -      if (offset < childSize) {
 | 
| -        return child.moveToOffset(offset);
 | 
| -      }
 | 
| -      offset -= childSize;
 | 
| -    }
 | 
| -    // TODO: nesting at this level
 | 
| -    return end;
 | 
| -  }
 | 
| -
 | 
| -  CodePosition positionFromPoint(int x, int y) {
 | 
| -    if (y < top) return start;
 | 
| -
 | 
| -    for (var child in children) {
 | 
| -      if (child.top <= y && (child.top + child.height) >= y) {
 | 
| -        return child.positionFromPoint(x, y - child.top);
 | 
| -      }
 | 
| -    }
 | 
| -    // TODO: next level of nesting...
 | 
| -    return end;
 | 
| -  }
 | 
| -
 | 
| -  CodePosition getPosition(int line, int column) {
 | 
| -    if (line < 0) return start;
 | 
| -    for (var child in children) {
 | 
| -      if (line < child.lineCount) return child.getPosition(line, column);
 | 
| -
 | 
| -      line -= child.lineCount;
 | 
| -    }
 | 
| -    return end; // TODO
 | 
| -  }
 | 
| -
 | 
| -  // These are local line/column to this block??????
 | 
| -  LineColumn getLineColumn(int offset) {
 | 
| -    if (offset < 0) return new LineColumn(0, 0);
 | 
| -
 | 
| -    int childLine = 0;
 | 
| -    for (var child in children) {
 | 
| -      var childSize = child.size;
 | 
| -      if (offset < childSize) {
 | 
| -        // TODO: This needs modification!!!
 | 
| -        var ret = child.getLineColumn(offset);
 | 
| -        return new LineColumn(ret.line + childLine, ret.column);
 | 
| -      }
 | 
| -      offset -= childSize;
 | 
| -      childLine += child.lineCount;
 | 
| -    }
 | 
| -    // TODO: nesting at this level
 | 
| -    return new LineColumn(lineCount, 0); // ??? wrong end column
 | 
| -  }
 | 
| -
 | 
| -  Point offsetToPoint(int offset) {
 | 
| -    if (offset < 0) return new Point(0, 0);
 | 
| -
 | 
| -    for (var child in children) {
 | 
| -      var childSize = child.size;
 | 
| -      if (offset < childSize) {
 | 
| -        var ret = child.offsetToPoint(offset);
 | 
| -        return new Point(ret.x, ret.y + child.top);
 | 
| -      }
 | 
| -      offset -= childSize;
 | 
| -    }
 | 
| -    // TODO: nesting at this level
 | 
| -    return new Point(0, top + height);
 | 
| -  }
 | 
| -}
 | 
| -
 | 
| -/**
 | 
| - * Pure text block - terminals.
 | 
| - */
 | 
| -class TextBlock extends CodeBlock {
 | 
| -  List<int> _lineStarts;
 | 
| -  String _text;
 | 
| -
 | 
| -  TextBlock(CodeBlock parent, this._text, int depth): super(parent, depth) {
 | 
| -    //window.console.warn('tb: "$_text"');
 | 
| -  }
 | 
| -
 | 
| -  int get size() => _text.length;
 | 
| -
 | 
| -  int get lineCount() => _lineStarts.length;
 | 
| -
 | 
| -  String get text() => _text;
 | 
| -
 | 
| -  void set text(String newText) {
 | 
| -    _text = newText;
 | 
| -    markDirty();
 | 
| -  }
 | 
| -
 | 
| -  void insertText(int offset, String newText) {
 | 
| -    _text = _text.substring(0, offset) + newText + _text.substring(offset);
 | 
| -    markDirty();
 | 
| -  }
 | 
| -
 | 
| -  void delete(int from, int to) {
 | 
| -    assert(from <= to);
 | 
| -    assert(to <= _text.length);
 | 
| -    markDirty();
 | 
| -
 | 
| -    if (to == _text.length) {
 | 
| -      if (from == 0) {
 | 
| -        _parent._removeChild(this);
 | 
| -      } else {
 | 
| -        _text = _text.substring(0, from);
 | 
| -      }
 | 
| -    } else {
 | 
| -      _text = _text.substring(0, from) + _text.substring(to);
 | 
| -    }
 | 
| -  }
 | 
| -
 | 
| -  int tokenizeInto(SourceFile src, int start) {
 | 
| -    _lineStarts = new List<int>();
 | 
| -    _lineStarts.add(start);
 | 
| -
 | 
| -    // classify my text and create siblings and parents as needed
 | 
| -    var html = new StringBuffer();
 | 
| -    Tokenizer tokenizer = new Tokenizer(src, /*skipWhitespace:*/false, start);
 | 
| -
 | 
| -    int depth = 0;
 | 
| -
 | 
| -    // TODO(jimhug): REALLY INEFFICIENT!
 | 
| -    void addLineStarts(Token token) {
 | 
| -      // TODO(jimhug): Should we just make the Tokenizer do this directly?
 | 
| -      final text = src.text;
 | 
| -      for (int index = token.start; index < token.end; index++) {
 | 
| -        if (text.charCodeAt(index) == 10/*'\n'*/) {
 | 
| -          _lineStarts.add(index - start + 1);
 | 
| -        }
 | 
| -      }
 | 
| -    }
 | 
| -
 | 
| -    // TODO(jimhug): Add whitespace blocks?
 | 
| -    while (true) {
 | 
| -      var token = tokenizer.next();
 | 
| -
 | 
| -      if (token.kind == TokenKind.END_OF_FILE) {
 | 
| -        _node.innerHTML = html.toString();
 | 
| -        if (start == 0) _text = src.text;
 | 
| -        else _text = src.text.substring(start);
 | 
| -        height = lineCount * LINE_HEIGHT;
 | 
| -        // update parent
 | 
| -        return -1;
 | 
| -      } else if (token.kind == TokenKind.WHITESPACE) {
 | 
| -        // TODO(jimhug): Special handling for pure whitespace divs?
 | 
| -        if (src.text.charCodeAt(token.end-1) == 10/*'\n'*/) {
 | 
| -          // Model 1 - create siblings at any "true" line break
 | 
| -          // -- set my  from source
 | 
| -          _text = src.text.substring(start, token.end);
 | 
| -          _node.innerHTML = html.toString();
 | 
| -          height = lineCount * LINE_HEIGHT;
 | 
| -          // update parent...
 | 
| -          return token.end;
 | 
| -        }
 | 
| -      } else if (token.kind == TokenKind.COMMENT) {
 | 
| -        // TODO(jimhug): These may be the most fun blocks to handle...
 | 
| -        addLineStarts(token);
 | 
| -      } else if (token.kind == TokenKind.STRING) {
 | 
| -        addLineStarts(token);
 | 
| -      } else if (token.kind == TokenKind.STRING_PART) {
 | 
| -        addLineStarts(token);
 | 
| -      }
 | 
| -
 | 
| -      final kind = classify(token);
 | 
| -      final stringClass = '';
 | 
| -      final text = htmlEscape(token.text);
 | 
| -      if (kind != null) {
 | 
| -        html.add('<span class="$kind $stringClass">$text</span>');
 | 
| -      } else {
 | 
| -        html.add('<span>$text</span>');
 | 
| -      }
 | 
| -    }
 | 
| -  }
 | 
| -
 | 
| -  _redraw() {
 | 
| -    if (!_dirty) return;
 | 
| -    _dirty = false;
 | 
| -
 | 
| -    var initialText = _text;
 | 
| -    var end = tokenizeInto(new SourceFile('fake.dart', _text), 0);
 | 
| -    if (_text.length < initialText.length) {
 | 
| -      // ??? How to know we want a new block ???
 | 
| -      var extraText = initialText.substring(_text.length);
 | 
| -      _parent.insertText(_parent.getOffset(this) + _text.length, extraText);
 | 
| -    }
 | 
| -  }
 | 
| -
 | 
| -  CodePosition moveToOffset(int offset) {
 | 
| -    if (offset < 0 || offset >= _text.length) {
 | 
| -      return _parent.moveToOffset(_parent.getOffset(this) + offset);
 | 
| -    }
 | 
| -    return new CodePosition(this, offset);
 | 
| -  }
 | 
| -
 | 
| -  CodePosition positionFromPoint(int x, int y) {
 | 
| -    return getPosition((y / LINE_HEIGHT).floor(), (x / CHAR_WIDTH).round());
 | 
| -  }
 | 
| -
 | 
| -  CodePosition getPosition(int line, int column) {
 | 
| -    if (line < 0 || line >= lineCount) {
 | 
| -      return _parent.getPosition(_parent.getLine(this) + line, column);
 | 
| -    }
 | 
| -
 | 
| -    int maxOffset;
 | 
| -    if (line < _lineStarts.length - 1) {
 | 
| -      maxOffset = _lineStarts[line + 1] - 1;
 | 
| -    } else {
 | 
| -      maxOffset = _text.length - 1;
 | 
| -    }
 | 
| -
 | 
| -    final offset = Math.min(_lineStarts[line] + column, maxOffset);
 | 
| -
 | 
| -    return new CodePosition(this, offset);
 | 
| -  }
 | 
| -
 | 
| -  // These are local line/column to this block
 | 
| -  LineColumn getLineColumn(int offset) {
 | 
| -    if (_lineStarts === null) {
 | 
| -      return new LineColumn(0, 0);
 | 
| -    }
 | 
| -    // TODO(jimhug): Binary search would be faster but more complicated.
 | 
| -    int previousStart = 0;
 | 
| -    int line = 1;
 | 
| -    for (; line < _lineStarts.length; line++) {
 | 
| -      int start = _lineStarts[line];
 | 
| -      if (start > offset) {
 | 
| -        break;
 | 
| -      }
 | 
| -      previousStart = start;
 | 
| -    }
 | 
| -    return new LineColumn(line - 1, offset - previousStart);
 | 
| -  }
 | 
| -
 | 
| -  Point offsetToPoint(int offset) {
 | 
| -    LineColumn lc = getLineColumn(offset);
 | 
| -    return new Point(lc.column * CHAR_WIDTH, top + (lc.line * LINE_HEIGHT));
 | 
| -  }
 | 
| -}
 | 
| -
 | 
| -
 | 
| -class CodeBlock {
 | 
| -  CodeBlock _parent;
 | 
| -
 | 
| -  CodeBlock _previousSibling;
 | 
| -  CodeBlock _nextSibling;
 | 
| -
 | 
| -  int _depth = 0;
 | 
| -
 | 
| -  bool _dirty = true;
 | 
| -  var _node;
 | 
| -  int _top, _height;
 | 
| -
 | 
| -  CodeBlock(this._parent, this._depth) {
 | 
| -    _node = document.createElement('div');
 | 
| -    _node.className = 'code'; // TODO - different kinds of nodes
 | 
| -  }
 | 
| -
 | 
| -  abstract int size();
 | 
| -  abstract int get lineCount();
 | 
| -
 | 
| -  abstract String get text();
 | 
| -
 | 
| -  abstract void set text(String newText);
 | 
| -
 | 
| -  abstract CodePosition moveToOffset(int offset);
 | 
| -
 | 
| -  CodePosition get start() => new CodePosition(this, 0);
 | 
| -
 | 
| -  CodePosition get end() => new CodePosition(this, size);
 | 
| -
 | 
| -  int getOffset(CodeBlock forChild) {
 | 
| -    throw "child missing";
 | 
| -  }
 | 
| -
 | 
| -  int getLine(CodeBlock forChild) {
 | 
| -    throw "child missing";
 | 
| -  }
 | 
| -
 | 
| -  _removeChild(CodeBlock child) {
 | 
| -    throw "child missing";
 | 
| -  }
 | 
| -
 | 
| -  void parse() {
 | 
| -    final source = new SourceFile('fake.dart', text);
 | 
| -    var p = new Parser(source);
 | 
| -    var cu = p.compilationUnit();
 | 
| -  }
 | 
| -
 | 
| -  void markDirty() {
 | 
| -    if (!_dirty) {
 | 
| -      _dirty = true;
 | 
| -      if (_parent != null) {
 | 
| -        _parent._dirty = true;
 | 
| -      }
 | 
| -    }
 | 
| -  }
 | 
| -
 | 
| -  int get top() => _top;
 | 
| -
 | 
| -  void set top(int newTop) {
 | 
| -    if (newTop != _top) {
 | 
| -      _top = newTop;
 | 
| -      _node.style.setProperty('top', '${_top}px');
 | 
| -    }
 | 
| -  }
 | 
| -
 | 
| -  int get height() => _height;
 | 
| -
 | 
| -  void set height(int newHeight) {
 | 
| -    if (newHeight != _height) {
 | 
| -      _height = newHeight;
 | 
| -      _node.style.setProperty('height', '${_height}px');
 | 
| -    }
 | 
| -  }
 | 
| -
 | 
| -  abstract void insertText(int offset, String newText);
 | 
| -
 | 
| -  abstract void delete(int from, int to);
 | 
| -
 | 
| -  CodePosition positionFromMouse(MouseEvent p) {
 | 
| -    var box = _node.getBoundingClientRect();
 | 
| -    int y = p.clientY - box.top;
 | 
| -    int x = p.clientX - box.left;
 | 
| -
 | 
| -    return positionFromPoint(x, y);
 | 
| -  }
 | 
| -
 | 
| -  abstract CodePosition positionFromPoint(int x, int y);
 | 
| -
 | 
| -  abstract CodePosition getPosition(int line, int column);
 | 
| -
 | 
| -  // These are local line/column to this block
 | 
| -  abstract LineColumn getLineColumn(int offset);
 | 
| -
 | 
| -  abstract Point offsetToPoint(int offset);
 | 
| -
 | 
| -  abstract void _redraw();
 | 
| -}
 | 
| -
 | 
| -
 | 
| -class KeyBindings {
 | 
| -  static final Map _remap = const {
 | 
| -    'U+001B':'Esc', 'U+0008':'Delete', 'U+0009':'Tab', 'U+0020':'Space',
 | 
| -    'Shift':'',  'Control':'', 'Alt':'', 'Meta':''
 | 
| -  };
 | 
| -
 | 
| -  static String _getModifiers(event) {
 | 
| -    String ret = '';
 | 
| -  	if (event.ctrlKey) { ret += 'Control-'; }
 | 
| -  	if (event.altKey) { ret += 'Alt-'; }
 | 
| -  	if (event.metaKey) { ret += 'Meta-'; }
 | 
| -  	if (event.shiftKey) { ret += 'Shift-'; }
 | 
| -  	return ret;
 | 
| -  }
 | 
| -
 | 
| -  // TODO(jimhug): Move this to base <= 36 and into shared code.
 | 
| -  static int _hexDigit(int c) {
 | 
| -    if(c >= 48/*0*/ && c <= 57/*9*/) {
 | 
| -      return c - 48;
 | 
| -    } else if (c >= 97/*a*/ && c <= 102/*f*/) {
 | 
| -      return c - 87;
 | 
| -    } else if (c >= 65/*A*/ && c <= 70/*F*/) {
 | 
| -      return c - 55;
 | 
| -    } else {
 | 
| -      return -1;
 | 
| -    }
 | 
| -  }
 | 
| -
 | 
| -  static int parseHex(String hex) {
 | 
| -    var result = 0;
 | 
| -
 | 
| -    for (int i=0; i < hex.length; i++) {
 | 
| -      var digit = _hexDigit(hex.charCodeAt(i));
 | 
| -      assert(digit != -1);
 | 
| -      result = (result << 4) + digit;
 | 
| -    }
 | 
| -
 | 
| -    return result;
 | 
| -  }
 | 
| -
 | 
| -  static String translate(event) {
 | 
| -    var ret = _remap[event.keyIdentifier];
 | 
| -    if (ret === null) ret = event.keyIdentifier;
 | 
| -
 | 
| -    if (ret == '') {
 | 
| -      return null;
 | 
| -    } else if (ret.startsWith('U+')) {
 | 
| -      // This method only reports "non-text" key presses
 | 
| -      if (event.ctrlKey || event.altKey || event.metaKey) {
 | 
| -  			return _getModifiers(event) +
 | 
| -  			  new String.fromCharCodes([parseHex(ret.substring(2, ret.length))]);
 | 
| -  		} else {
 | 
| -  			return null;
 | 
| -  		}
 | 
| -    } else {
 | 
| -      return _getModifiers(event) + ret;
 | 
| -    }
 | 
| -  }
 | 
| -
 | 
| -  var node;
 | 
| -  Map bindings;
 | 
| -  var handleText, handleUnknown;
 | 
| -
 | 
| -  KeyBindings(this.node, this.bindings, this.handleText, this.handleUnknown) {
 | 
| -    node.addEventListener('textInput', onTextInput, false);
 | 
| -    node.addEventListener('keydown', onKeydown, false);
 | 
| -  }
 | 
| -
 | 
| -  onTextInput(TextEvent event) {
 | 
| -    var text = event.data;
 | 
| -    var ret;
 | 
| -    if (bindings[text] !== null) {
 | 
| -      ret = bindings[text]();
 | 
| -    } else {
 | 
| -      ret = handleText(text);
 | 
| -    }
 | 
| -    // TODO(jimhug): Unfortunate coupling to shell.
 | 
| -    shell._editor._redraw();
 | 
| -    return ret;
 | 
| -  }
 | 
| -
 | 
| -  // TODO(jimhug): KeyboardEvent type is needed!
 | 
| -  onKeydown(KeyboardEvent event) {
 | 
| -    final key = translate(event);
 | 
| -    if (key !== null) {
 | 
| -      if (bindings[key] !== null) {
 | 
| -				bindings[key]();
 | 
| -				event.preventDefault();
 | 
| -			} else {
 | 
| -				if (handleUnknown(key)) {
 | 
| -				  event.preventDefault();
 | 
| -				} else {
 | 
| -				  event.stopPropagation();
 | 
| -				}
 | 
| -			}
 | 
| -    } else {
 | 
| -      event.stopPropagation();
 | 
| -    }
 | 
| -    // TODO(jimhug): Unfortunate coupling to shell.
 | 
| -    shell._editor._redraw();
 | 
| -    return false;
 | 
| -  }
 | 
| -}
 | 
| -
 | 
| -
 | 
| -
 | 
| -// TODO(jimhug): Copy, paste and then modified from dartdoc
 | 
| -/**
 | 
| - * Kinds of tokens that we care to highlight differently. The values of the
 | 
| - * fields here will be used as CSS class names for the generated spans.
 | 
| - */
 | 
| -class Classification {
 | 
| -  static final NONE = null;
 | 
| -  static final ERROR = "e";
 | 
| -  static final COMMENT = "c";
 | 
| -  static final IDENTIFIER = "i";
 | 
| -  static final KEYWORD = "k";
 | 
| -  static final OPERATOR = "o";
 | 
| -  static final STRING = "s";
 | 
| -  static final NUMBER = "n";
 | 
| -  static final PUNCTUATION = "p";
 | 
| -
 | 
| -  // A few things that are nice to make different:
 | 
| -  static final TYPE_IDENTIFIER = "t";
 | 
| -
 | 
| -  // Between a keyword and an identifier
 | 
| -  static final SPECIAL_IDENTIFIER = "r";
 | 
| -
 | 
| -  static final ARROW_OPERATOR = "a";
 | 
| -
 | 
| -  static final STRING_INTERPOLATION = 'si';
 | 
| -}
 | 
| -
 | 
| -// TODO(rnystrom): should exist in standard lib somewhere
 | 
| -String htmlEscape(String text) {
 | 
| -  return text.replaceAll('&', '&').replaceAll(
 | 
| -      '>', '>').replaceAll('<', '<');
 | 
| -}
 | 
| -
 | 
| -bool _looksLikeType(String name) {
 | 
| -  // If the name looks like an UppercaseName, assume it's a type.
 | 
| -  return _looksLikePublicType(name) || _looksLikePrivateType(name);
 | 
| -}
 | 
| -
 | 
| -bool _looksLikePublicType(String name) {
 | 
| -  // If the name looks like an UppercaseName, assume it's a type.
 | 
| -  return name.length >= 2 && isUpper(name[0]) && isLower(name[1]);
 | 
| -}
 | 
| -
 | 
| -bool _looksLikePrivateType(String name) {
 | 
| -  // If the name looks like an _UppercaseName, assume it's a type.
 | 
| -  return (name.length >= 3 && name[0] == '_' && isUpper(name[1])
 | 
| -    && isLower(name[2]));
 | 
| -}
 | 
| -
 | 
| -// These ensure that they don't return "true" if the string only has symbols.
 | 
| -bool isUpper(String s) => s.toLowerCase() != s;
 | 
| -bool isLower(String s) => s.toUpperCase() != s;
 | 
| -
 | 
| -String classify(Token token) {
 | 
| -  switch (token.kind) {
 | 
| -    case TokenKind.ERROR:
 | 
| -      return Classification.ERROR;
 | 
| -
 | 
| -    case TokenKind.IDENTIFIER:
 | 
| -      // Special case for names that look like types.
 | 
| -      if (_looksLikeType(token.text)
 | 
| -          || token.text == 'num'
 | 
| -          || token.text == 'bool'
 | 
| -          || token.text == 'int'
 | 
| -          || token.text == 'double') {
 | 
| -        return Classification.TYPE_IDENTIFIER;
 | 
| -      }
 | 
| -      return Classification.IDENTIFIER;
 | 
| -
 | 
| -    // Even though it's a reserved word, let's try coloring it like a type.
 | 
| -    case TokenKind.VOID:
 | 
| -      return Classification.TYPE_IDENTIFIER;
 | 
| -
 | 
| -    case TokenKind.THIS:
 | 
| -    case TokenKind.SUPER:
 | 
| -      return Classification.SPECIAL_IDENTIFIER;
 | 
| -
 | 
| -    case TokenKind.STRING:
 | 
| -    case TokenKind.STRING_PART:
 | 
| -    case TokenKind.INCOMPLETE_STRING:
 | 
| -    case TokenKind.INCOMPLETE_MULTILINE_STRING_DQ:
 | 
| -    case TokenKind.INCOMPLETE_MULTILINE_STRING_SQ:
 | 
| -      return Classification.STRING;
 | 
| -
 | 
| -    case TokenKind.INTEGER:
 | 
| -    case TokenKind.HEX_INTEGER:
 | 
| -    case TokenKind.DOUBLE:
 | 
| -      return Classification.NUMBER;
 | 
| -
 | 
| -    case TokenKind.COMMENT:
 | 
| -    case TokenKind.INCOMPLETE_COMMENT:
 | 
| -      return Classification.COMMENT;
 | 
| -
 | 
| -    // => is so awesome it is in a class of its own.
 | 
| -    case TokenKind.ARROW:
 | 
| -      return Classification.ARROW_OPERATOR;
 | 
| -
 | 
| -    case TokenKind.HASHBANG:
 | 
| -    case TokenKind.LPAREN:
 | 
| -    case TokenKind.RPAREN:
 | 
| -    case TokenKind.LBRACK:
 | 
| -    case TokenKind.RBRACK:
 | 
| -    case TokenKind.LBRACE:
 | 
| -    case TokenKind.RBRACE:
 | 
| -    case TokenKind.COLON:
 | 
| -    case TokenKind.SEMICOLON:
 | 
| -    case TokenKind.COMMA:
 | 
| -    case TokenKind.DOT:
 | 
| -    case TokenKind.ELLIPSIS:
 | 
| -      return Classification.PUNCTUATION;
 | 
| -
 | 
| -    case TokenKind.INCR:
 | 
| -    case TokenKind.DECR:
 | 
| -    case TokenKind.BIT_NOT:
 | 
| -    case TokenKind.NOT:
 | 
| -    case TokenKind.ASSIGN:
 | 
| -    case TokenKind.ASSIGN_OR:
 | 
| -    case TokenKind.ASSIGN_XOR:
 | 
| -    case TokenKind.ASSIGN_AND:
 | 
| -    case TokenKind.ASSIGN_SHL:
 | 
| -    case TokenKind.ASSIGN_SAR:
 | 
| -    case TokenKind.ASSIGN_SHR:
 | 
| -    case TokenKind.ASSIGN_ADD:
 | 
| -    case TokenKind.ASSIGN_SUB:
 | 
| -    case TokenKind.ASSIGN_MUL:
 | 
| -    case TokenKind.ASSIGN_DIV:
 | 
| -    case TokenKind.ASSIGN_TRUNCDIV:
 | 
| -    case TokenKind.ASSIGN_MOD:
 | 
| -    case TokenKind.CONDITIONAL:
 | 
| -    case TokenKind.OR:
 | 
| -    case TokenKind.AND:
 | 
| -    case TokenKind.BIT_OR:
 | 
| -    case TokenKind.BIT_XOR:
 | 
| -    case TokenKind.BIT_AND:
 | 
| -    case TokenKind.SHL:
 | 
| -    case TokenKind.SAR:
 | 
| -    case TokenKind.SHR:
 | 
| -    case TokenKind.ADD:
 | 
| -    case TokenKind.SUB:
 | 
| -    case TokenKind.MUL:
 | 
| -    case TokenKind.DIV:
 | 
| -    case TokenKind.TRUNCDIV:
 | 
| -    case TokenKind.MOD:
 | 
| -    case TokenKind.EQ:
 | 
| -    case TokenKind.NE:
 | 
| -    case TokenKind.EQ_STRICT:
 | 
| -    case TokenKind.NE_STRICT:
 | 
| -    case TokenKind.LT:
 | 
| -    case TokenKind.GT:
 | 
| -    case TokenKind.LTE:
 | 
| -    case TokenKind.GTE:
 | 
| -    case TokenKind.INDEX:
 | 
| -    case TokenKind.SETINDEX:
 | 
| -      return Classification.OPERATOR;
 | 
| -
 | 
| -    // Color this like a keyword
 | 
| -    case TokenKind.HASH:
 | 
| -
 | 
| -    case TokenKind.ABSTRACT:
 | 
| -    case TokenKind.ASSERT:
 | 
| -    case TokenKind.CLASS:
 | 
| -    case TokenKind.EXTENDS:
 | 
| -    case TokenKind.FACTORY:
 | 
| -    case TokenKind.GET:
 | 
| -    case TokenKind.IMPLEMENTS:
 | 
| -    case TokenKind.IMPORT:
 | 
| -    case TokenKind.INTERFACE:
 | 
| -    case TokenKind.LIBRARY:
 | 
| -    case TokenKind.NATIVE:
 | 
| -    case TokenKind.NEGATE:
 | 
| -    case TokenKind.OPERATOR:
 | 
| -    case TokenKind.SET:
 | 
| -    case TokenKind.SOURCE:
 | 
| -    case TokenKind.STATIC:
 | 
| -    case TokenKind.TYPEDEF:
 | 
| -    case TokenKind.BREAK:
 | 
| -    case TokenKind.CASE:
 | 
| -    case TokenKind.CATCH:
 | 
| -    case TokenKind.CONST:
 | 
| -    case TokenKind.CONTINUE:
 | 
| -    case TokenKind.DEFAULT:
 | 
| -    case TokenKind.DO:
 | 
| -    case TokenKind.ELSE:
 | 
| -    case TokenKind.FALSE:
 | 
| -    case TokenKind.FINALLY:
 | 
| -    case TokenKind.FOR:
 | 
| -    case TokenKind.IF:
 | 
| -    case TokenKind.IN:
 | 
| -    case TokenKind.IS:
 | 
| -    case TokenKind.NEW:
 | 
| -    case TokenKind.NULL:
 | 
| -    case TokenKind.RETURN:
 | 
| -    case TokenKind.SWITCH:
 | 
| -    case TokenKind.THROW:
 | 
| -    case TokenKind.TRUE:
 | 
| -    case TokenKind.TRY:
 | 
| -    case TokenKind.WHILE:
 | 
| -    case TokenKind.VAR:
 | 
| -    case TokenKind.FINAL:
 | 
| -      return Classification.KEYWORD;
 | 
| -
 | 
| -    case TokenKind.WHITESPACE:
 | 
| -    case TokenKind.END_OF_FILE:
 | 
| -      return Classification.NONE;
 | 
| -
 | 
| -    default:
 | 
| -      return Classification.NONE;
 | 
| -  }
 | 
| -}
 | 
| 
 |