| 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;
|
| - }
|
| -}
|
|
|