| // Copyright (c) 2012, 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. |
| |
| compilerIsolate(port) { |
| Runner runner = new Runner(); |
| runner.init(); |
| |
| port.receive((msg, replyTo) { |
| replyTo.send(runner.update(msg)); |
| }); |
| } |
| |
| main() { |
| html.document.query('#status').innerHTML = 'Initializing...'; |
| setOutline(msg) { |
| html.document.query('#out').innerHTML = msg; |
| } |
| final codeDiv = html.document.query('#code'); |
| final popup = html.document.query('#popup'); |
| |
| // The port for communicating with the compiler isolate. |
| var port = null; |
| |
| // Should be called when [codeDiv] has changed. For now, call it always |
| // on mouse up and key up events. |
| update() { |
| if (port == null) return; |
| port.call(codeDiv.text).then(setOutline); |
| } |
| |
| hide(Element e) { |
| e.style.visibility = 'hidden'; |
| } |
| |
| show(Element e) { |
| e.style.visibility = 'visible'; |
| } |
| |
| insertTextAtPoint(Event e, String newText) { |
| e.preventDefault(); |
| var selection = html.window.getSelection(); |
| var range = selection.getRangeAt(0); |
| var offset = range.startOffset; |
| Text text = codeDiv.nodes[0]; |
| text.insertData(offset, newText); |
| selection.setPosition(text, offset + newText.length); |
| } |
| |
| // This prevents creating a new div element when hitting enter. |
| codeDiv.on.keyPress.add((Event e) { |
| if (e.keyIdentifier == 'Enter') { |
| // TODO(ahe): Is 'Enter' portable? |
| insertTextAtPoint(e, '\n'); |
| } |
| }); |
| |
| // Override tab key. |
| codeDiv.on.keyDown.add((Event e) { |
| if (e.keyIdentifier == 'U+0009') { |
| // TODO(ahe): Find better way to detect tab key. |
| insertTextAtPoint(e, ' '); |
| } |
| }); |
| |
| // Called on keyUp and mouseUp to display a marker at the current |
| // insertion point (the selection's first range). This probably |
| // needs more work before it works really well, but seems to be good |
| // enough for now. |
| handleUp(Event e) { |
| var selection = html.window.getSelection(); |
| var range = selection.getRangeAt(0); |
| var rects = range.getClientRects(); |
| if (rects.length < 1) { |
| hide(popup); |
| return; |
| } |
| html.ClientRect rect = rects.item(rects.length - 1); |
| if (rect.width.toInt() != 0) { |
| // This is a selection of multiple characters, not a single |
| // point of insertion. |
| hide(popup); |
| return; |
| } |
| popup.style.top = "${rect.bottom.toInt()}px"; |
| popup.style.left = "${rect.right.toInt()}px"; |
| // Instead of displaying this immediately, we could set up a timer |
| // event and display it later. This is a matter of getting the UX |
| // just right, for now it simply demonstrates that we know where |
| // the insertion point is (in pixels) and what the character |
| // offset is. |
| show(popup); |
| popup.text = '''Code completion here... |
| Current character offset: ${range.startOffset}'''; |
| // TODO(ahe): Better detection of when [codeDiv] has changed. |
| update(); |
| } |
| |
| codeDiv.on.keyUp.add(handleUp); |
| codeDiv.on.mouseUp.add(handleUp); |
| |
| codeDiv.on.blur.add((Event e) => hide(popup)); |
| |
| // The event handlers are now set up. Allow editing. |
| codeDiv.contentEditable = "true"; |
| |
| // Creates a compiler isolate in its own iframe. This should prevent |
| // the compiler from blocking the UI. |
| spawnDomIsolate(html.document.query('#isolate').contentWindow, |
| 'compilerIsolate').then((sendPort) { |
| // The compiler isolate is now ready to talk. Store the port so |
| // that the update function starts requesting outlines whenever |
| // [codeDiv] changes. |
| port = sendPort; |
| // Make sure that we get an initial outline. |
| update(); |
| html.document.query('#status').innerHTML = 'Ready'; |
| }); |
| } |
| |
| class Runner { |
| final LeapCompiler compiler; |
| |
| Runner() : compiler = new LeapCompiler(); |
| |
| String init() { |
| Stopwatch sw = new Stopwatch()..start(); |
| // TODO(rnystrom): This is broken now that scanBuiltInLibraries is async. |
| // Delete this sample. |
| compiler.scanBuiltinLibraries(); |
| sw.stop(); |
| return 'Scanned core libraries in ${sw.elapsedMilliseconds}ms'; |
| } |
| |
| String update(String codeText) { |
| StringBuffer sb = new StringBuffer(); |
| |
| Stopwatch sw = new Stopwatch()..start(); |
| |
| LibraryElement e = compile(new LeapScript(codeText)); |
| |
| void printFunction(FunctionElement fe, [String indentation = ""]) { |
| var paramAcc = []; |
| |
| FunctionType ft = fe.computeType(compiler); |
| |
| sb.write("<div>${indentation}"); |
| ft.returnType.name.printOn(sb); |
| sb.write(" "); |
| fe.name.printOn(sb); |
| sb.write("("); |
| ft.parameterTypes.printOn(sb, ", "); |
| sb.write(");</div>"); |
| |
| } |
| |
| void printField(FieldElement fe, [String indentation = ""]) { |
| sb.write("<div>${indentation}var "); |
| fe.name.printOn(sb); |
| sb.write(";</div>"); |
| } |
| |
| void printClass(ClassElement ce) { |
| ce.parseNode(compiler); |
| |
| sb.write("<div>class "); |
| ce.name.printOn(sb); |
| sb.write(" {"); |
| |
| for (Element e in ce.members.reverse()) { |
| switch(e.kind) { |
| case ElementKind.FUNCTION: |
| |
| printFunction(e, " "); |
| break; |
| |
| case ElementKind.FIELD: |
| |
| printField(e, " "); |
| break; |
| } |
| } |
| sb.write("}</div>"); |
| } |
| |
| for (Element c in e.topLevelElements.reverse()) { |
| switch (c.kind) { |
| case ElementKind.FUNCTION: |
| printFunction (c); |
| break; |
| |
| case ElementKind.CLASS: |
| printClass(c); |
| break; |
| |
| case ElementKind.FIELD: |
| printField (c); |
| break; |
| } |
| } |
| |
| compiler.log("Outline ${sw.elapsedMilliseconds}"); |
| return sb.toString(); |
| } |
| |
| Element compile(String script) { |
| return compiler.runSelective(script); |
| } |
| } |
| |
| class LeapCompiler extends Compiler { |
| HttpRequestCache cache; |
| |
| final bool throwOnError = false; |
| |
| final libDir = "../.."; |
| |
| LeapCompiler() : cache = new HttpRequestCache(), super() { |
| tasks = [scanner, dietParser, parser, resolver, checker]; |
| } |
| |
| void log(message) { print(message); } |
| |
| String get legDirectory => libDir; |
| |
| // TODO(rnystrom): This is broken now that scanBuiltInLibraries is async. |
| // Delete this sample. |
| LibraryElement scanBuiltinLibrary(String path) { |
| Uri base = Uri.parse(html.window.location.toString()); |
| Uri libraryRoot = base.resolve(libDir); |
| Uri resolved = libraryRoot.resolve(DART2JS_LIBRARY_MAP[path]); |
| // TODO(rnystrom): This is broken now that scanBuiltInLibraries is async. |
| // Delete this sample. |
| LibraryElement library = scanner.loadLibrary(resolved, null); |
| return library; |
| } |
| |
| currentScript() { |
| if (currentElement == null) return null; |
| CompilationUnitElement compilationUnit = |
| currentElement.getCompilationUnit(); |
| if (compilationUnit == null) return null; |
| return compilationUnit.script; |
| } |
| |
| // TODO(rnystrom): This is broken now that scanBuiltInLibraries is async. |
| // Delete this sample. |
| Script readScript(Uri uri, [ScriptTag node]) { |
| String text = ""; |
| try { |
| text = cache.readAll(uri.path.toString()); |
| } catch (exception) { |
| cancel("${uri.path}: $exception", node: node); |
| } |
| SourceFile sourceFile = new SourceFile(uri.toString(), text); |
| return new Script(uri, sourceFile); |
| } |
| |
| reportWarning(Node node, var message) { |
| print(message); |
| } |
| |
| reportError(Node node, var message) { |
| cancel(message.toString(), node); |
| } |
| |
| void cancel(String reason, {Node node, token, instruction, element}) { |
| print(reason); |
| } |
| |
| Element runSelective(Script script) { |
| Stopwatch sw = new Stopwatch()..start(); |
| Element e; |
| try { |
| e = runCompilerSelective(script); |
| } on CompilerCancelledException catch (exception) { |
| log(exception.toString()); |
| log('compilation failed'); |
| return null; |
| } |
| log('compilation succeeded: ${sw.elapsedMilliseconds}ms'); |
| return e; |
| } |
| |
| LibraryElement runCompilerSelective(Script script) { |
| mainApp = new LibraryElement(script); |
| |
| universe.libraries.remove(script.uri.toString()); |
| Element element; |
| withCurrentElement(mainApp, () { |
| scanner.scan(mainApp); |
| }); |
| return mainApp; |
| } |
| } |