Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library trydart.editor; | 5 library trydart.editor; |
| 6 | 6 |
| 7 import 'dart:html'; | 7 import 'dart:html'; |
| 8 | 8 |
| 9 import '../../../sdk/lib/_internal/compiler/implementation/scanner/scannerlib.da rt' | 9 import 'package:compiler/implementation/scanner/scannerlib.dart' |
| 10 show | 10 show |
| 11 EOF_TOKEN, | 11 EOF_TOKEN, |
| 12 StringScanner, | 12 StringScanner, |
| 13 Token; | 13 Token; |
| 14 | 14 |
| 15 import '../../../sdk/lib/_internal/compiler/implementation/source_file.dart' sho w | |
| 16 StringSourceFile; | |
| 17 | |
| 18 import 'compilation.dart' show | |
| 19 scheduleCompilation; | |
| 20 | |
| 21 import 'ui.dart' show | 15 import 'ui.dart' show |
| 22 currentTheme, | 16 currentTheme, |
| 23 hackDiv, | 17 hackDiv, |
| 24 inputPre, | 18 inputPre, |
| 25 observer, | 19 observer, |
| 26 outputDiv; | 20 outputDiv; |
| 27 | 21 |
| 28 import 'decoration.dart' show | 22 import 'decoration.dart' show |
| 23 CodeCompletionDecoration, | |
| 29 Decoration, | 24 Decoration, |
| 30 DiagnosticDecoration, | 25 DiagnosticDecoration, |
| 31 error, | 26 error, |
| 32 info, | 27 info, |
| 33 warning; | 28 warning; |
| 34 | 29 |
| 35 const String INDENT = '\u{a0}\u{a0}'; | 30 const String INDENT = '\u{a0}\u{a0}'; |
| 36 | 31 |
| 37 onKeyUp(KeyboardEvent e) { | 32 Set<String> seenIdentifiers; |
| 38 if (e.keyCode == 13) { | 33 |
| 39 e.preventDefault(); | 34 Element moveActive(int distance) { |
| 40 Selection selection = window.getSelection(); | 35 List<Element> entries = document.querySelectorAll('.dart-static>.dart-entry'); |
| 41 if (selection.isCollapsed && selection.anchorNode is Text) { | 36 int activeIndex = -1; |
| 42 Text text = selection.anchorNode; | 37 for (var i = 0; i < entries.length; i++) { |
| 43 int offset = selection.anchorOffset; | 38 if (entries[i].classes.contains('activeEntry')) { |
| 44 text.insertData(offset, '\n'); | 39 activeIndex = i; |
| 45 selection.collapse(text, offset + 1); | 40 break; |
| 46 } | 41 } |
| 47 } | 42 } |
| 48 // This is a hack to get Safari to send mutation events on contenteditable. | 43 int newIndex = activeIndex + distance; |
| 49 var newDiv = new DivElement(); | 44 Element currentEntry; |
| 50 hackDiv.replaceWith(newDiv); | 45 if (0 <= newIndex && newIndex < entries.length) { |
| 51 hackDiv = newDiv; | 46 currentEntry = entries[newIndex]; |
| 47 } | |
| 48 if (currentEntry == null) return null; | |
| 49 if (0 <= newIndex && activeIndex != -1) { | |
| 50 entries[activeIndex].classes.remove('activeEntry'); | |
| 51 } | |
| 52 Element staticNode = document.querySelector('.dart-static'); | |
| 53 String visibility = computeVisibility(currentEntry, staticNode); | |
| 54 print(visibility); | |
| 55 var serverResults = document.querySelectorAll('.dart-server>.dart-entry'); | |
| 56 var serverResultCount = serverResults.length; | |
| 57 if (serverResultCount > 0) { | |
| 58 switch (visibility) { | |
| 59 case obscured: | |
| 60 case hidden: { | |
|
kasperl
2014/03/24 10:32:51
No need for { } in a case.
ahe
2014/03/24 15:20:23
I like {} in this case.
| |
| 61 Rectangle cr = currentEntry.getBoundingClientRect(); | |
| 62 Rectangle sr = staticNode.getBoundingClientRect(); | |
| 63 Element entry = serverResults[0]; | |
| 64 entry.remove(); | |
| 65 currentEntry.parentNode.insertBefore(entry, currentEntry); | |
| 66 currentEntry = entry; | |
| 67 serverResultCount--; | |
| 68 | |
| 69 staticNode.style.maxHeight = '${sr.boundingBox(cr).height}px'; | |
| 70 } | |
| 71 } | |
| 72 } else { | |
| 73 currentEntry.scrollIntoView(ScrollAlignment.BOTTOM); | |
| 74 } | |
| 75 if (serverResultCount == 0) { | |
| 76 document.querySelector('.dart-server').style.display = 'none'; | |
| 77 } | |
| 78 if (currentEntry != null) { | |
| 79 currentEntry.classes.add('activeEntry'); | |
| 80 } | |
| 81 // Discard mutations. | |
| 82 observer.takeRecords(); | |
| 83 return currentEntry; | |
| 84 } | |
| 85 | |
| 86 const visible = 'visible'; | |
| 87 const obscured = 'obscured'; | |
| 88 const hidden = 'hidden'; | |
| 89 | |
| 90 String computeVisibility(Element node, [Element parent]) { | |
| 91 Rectangle nr = node.getBoundingClientRect(); | |
| 92 if (parent == null) parent = node.parentNode; | |
| 93 Rectangle pr = parent.getBoundingClientRect(); | |
| 94 | |
| 95 if (pr.containsRectangle(nr)) return visible; | |
| 96 | |
| 97 if (pr.intersects(nr)) return obscured; | |
| 98 | |
| 99 return hidden; | |
| 100 } | |
| 101 | |
| 102 var activeCompletion; | |
| 103 num minSuggestionWidth = 0; | |
| 104 | |
| 105 /// Returns the [Element] which encloses the current collapsed selection, if it | |
| 106 /// exists. | |
| 107 Element getElementAtSelection() { | |
| 108 Selection selection = window.getSelection(); | |
| 109 if (!selection.isCollapsed) return null; | |
| 110 var anchorNode = selection.anchorNode; | |
| 111 if (!inputPre.contains(anchorNode)) return null; | |
| 112 if (inputPre == anchorNode) return null; | |
| 113 int type = anchorNode.nodeType; | |
| 114 if (type != Node.TEXT_NODE) return null; | |
| 115 Text text = anchorNode; | |
| 116 var parent = text.parent; | |
| 117 if (parent is! Element) return null; | |
| 118 if (inputPre == parent) return null; | |
| 119 return parent; | |
| 52 } | 120 } |
| 53 | 121 |
| 54 bool isMalformedInput = false; | 122 bool isMalformedInput = false; |
| 55 String currentSource = ""; | 123 String currentSource = ""; |
| 56 | 124 |
| 57 // TODO(ahe): This method should be cleaned up. It is too large. | |
| 58 onMutation(List<MutationRecord> mutations, MutationObserver observer) { | |
| 59 scheduleCompilation(); | |
| 60 | |
| 61 for (Element element in inputPre.queryAll('a[class="diagnostic"]>span')) { | |
| 62 element.remove(); | |
| 63 } | |
| 64 // Discard clean-up mutations. | |
| 65 observer.takeRecords(); | |
| 66 | |
| 67 Selection selection = window.getSelection(); | |
| 68 | |
| 69 while (!mutations.isEmpty) { | |
| 70 for (MutationRecord record in mutations) { | |
| 71 String type = record.type; | |
| 72 switch (type) { | |
| 73 | |
| 74 case 'characterData': | |
| 75 | |
| 76 bool hasSelection = false; | |
| 77 int offset = selection.anchorOffset; | |
| 78 if (selection.isCollapsed && selection.anchorNode == record.target) { | |
| 79 hasSelection = true; | |
| 80 } | |
| 81 var parent = record.target.parentNode; | |
| 82 if (parent != inputPre) { | |
| 83 inlineChildren(parent); | |
| 84 } | |
| 85 if (hasSelection) { | |
| 86 selection.collapse(record.target, offset); | |
| 87 } | |
| 88 break; | |
| 89 | |
| 90 default: | |
| 91 if (!record.addedNodes.isEmpty) { | |
| 92 for (var node in record.addedNodes) { | |
| 93 | |
| 94 if (node.nodeType != Node.ELEMENT_NODE) continue; | |
| 95 | |
| 96 if (node is BRElement) { | |
| 97 if (selection.anchorNode != node) { | |
| 98 node.replaceWith(new Text('\n')); | |
| 99 } | |
| 100 } else { | |
| 101 var parent = node.parentNode; | |
| 102 if (parent == null) continue; | |
| 103 var nodes = new List.from(node.nodes); | |
| 104 var style = node.getComputedStyle(); | |
| 105 if (style.display != 'inline') { | |
| 106 var previous = node.previousNode; | |
| 107 if (previous is Text) { | |
| 108 previous.appendData('\n'); | |
| 109 } else { | |
| 110 parent.insertBefore(new Text('\n'), node); | |
| 111 } | |
| 112 } | |
| 113 for (Node child in nodes) { | |
| 114 child.remove(); | |
| 115 parent.insertBefore(child, node); | |
| 116 } | |
| 117 node.remove(); | |
| 118 } | |
| 119 } | |
| 120 } | |
| 121 } | |
| 122 } | |
| 123 mutations = observer.takeRecords(); | |
| 124 } | |
| 125 | |
| 126 if (!inputPre.nodes.isEmpty && inputPre.nodes.last is Text) { | |
| 127 Text text = inputPre.nodes.last; | |
| 128 if (!text.text.endsWith('\n')) { | |
| 129 text.appendData('\n'); | |
| 130 } | |
| 131 } | |
| 132 | |
| 133 int offset = 0; | |
| 134 int anchorOffset = 0; | |
| 135 bool hasSelection = false; | |
| 136 Node anchorNode = selection.anchorNode; | |
| 137 // TODO(ahe): Try to share walk4 methods. | |
| 138 void walk4(Node node) { | |
| 139 // TODO(ahe): Use TreeWalker when that is exposed. | |
| 140 // function textNodesUnder(root){ | |
| 141 // var n, a=[], walk=document.createTreeWalker( | |
| 142 // root,NodeFilter.SHOW_TEXT,null,false); | |
| 143 // while(n=walk.nextNode()) a.push(n); | |
| 144 // return a; | |
| 145 // } | |
| 146 int type = node.nodeType; | |
| 147 if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE) { | |
| 148 CharacterData text = node; | |
| 149 if (anchorNode == node) { | |
| 150 hasSelection = true; | |
| 151 anchorOffset = selection.anchorOffset + offset; | |
| 152 return; | |
| 153 } | |
| 154 offset += text.length; | |
| 155 } | |
| 156 | |
| 157 var child = node.firstChild; | |
| 158 while (child != null) { | |
| 159 walk4(child); | |
| 160 if (hasSelection) return; | |
| 161 child = child.nextNode; | |
| 162 } | |
| 163 } | |
| 164 if (selection.isCollapsed) { | |
| 165 walk4(inputPre); | |
| 166 } | |
| 167 | |
| 168 currentSource = inputPre.text; | |
| 169 inputPre.nodes.clear(); | |
| 170 inputPre.appendText(currentSource); | |
| 171 if (hasSelection) { | |
| 172 selection.collapse(inputPre.firstChild, anchorOffset); | |
| 173 } | |
| 174 | |
| 175 isMalformedInput = false; | |
| 176 for (var n in new List.from(inputPre.nodes)) { | |
| 177 if (n is! Text) continue; | |
| 178 Text node = n; | |
| 179 String text = node.text; | |
| 180 | |
| 181 Token token = new StringScanner( | |
| 182 new StringSourceFile('', text), includeComments: true).tokenize(); | |
| 183 int offset = 0; | |
| 184 for (;token.kind != EOF_TOKEN; token = token.next) { | |
| 185 Decoration decoration = getDecoration(token); | |
| 186 if (decoration == null) continue; | |
| 187 bool hasSelection = false; | |
| 188 int selectionOffset = selection.anchorOffset; | |
| 189 | |
| 190 if (selection.isCollapsed && selection.anchorNode == node) { | |
| 191 hasSelection = true; | |
| 192 selectionOffset = selection.anchorOffset; | |
| 193 } | |
| 194 int splitPoint = token.charOffset - offset; | |
| 195 Text str = node.splitText(splitPoint); | |
| 196 Text after = str.splitText(token.charCount); | |
| 197 offset += splitPoint + token.charCount; | |
| 198 inputPre.insertBefore(after, node.nextNode); | |
| 199 inputPre.insertBefore(decoration.applyTo(str), after); | |
| 200 | |
| 201 if (hasSelection && selectionOffset > node.length) { | |
| 202 selectionOffset -= node.length; | |
| 203 if (selectionOffset > str.length) { | |
| 204 selectionOffset -= str.length; | |
| 205 selection.collapse(after, selectionOffset); | |
| 206 } else { | |
| 207 selection.collapse(str, selectionOffset); | |
| 208 } | |
| 209 } | |
| 210 node = after; | |
| 211 } | |
| 212 } | |
| 213 | |
| 214 window.localStorage['currentSource'] = currentSource; | |
| 215 | |
| 216 // Discard highlighting mutations. | |
| 217 observer.takeRecords(); | |
| 218 } | |
| 219 | |
| 220 addDiagnostic(String kind, String message, int begin, int end) { | 125 addDiagnostic(String kind, String message, int begin, int end) { |
| 221 observer.disconnect(); | 126 observer.disconnect(); |
| 222 Selection selection = window.getSelection(); | 127 Selection selection = window.getSelection(); |
| 223 int offset = 0; | 128 int offset = 0; |
| 224 int anchorOffset = 0; | 129 int anchorOffset = 0; |
| 225 bool hasSelection = false; | 130 bool hasSelection = false; |
| 226 Node anchorNode = selection.anchorNode; | 131 Node anchorNode = selection.anchorNode; |
| 227 bool foundNode = false; | 132 bool foundNode = false; |
| 228 void walk4(Node node) { | 133 void walk4(Node node) { |
| 229 // TODO(ahe): Use TreeWalker when that is exposed. | 134 // TODO(ahe): Use TreeWalker when that is exposed. |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 253 } | 158 } |
| 254 if (hasSelection) { | 159 if (hasSelection) { |
| 255 selection.collapse(node, anchorOffset); | 160 selection.collapse(node, anchorOffset); |
| 256 } | 161 } |
| 257 foundNode = true; | 162 foundNode = true; |
| 258 return; | 163 return; |
| 259 } | 164 } |
| 260 offset = newOffset; | 165 offset = newOffset; |
| 261 } else if (type == Node.ELEMENT_NODE) { | 166 } else if (type == Node.ELEMENT_NODE) { |
| 262 Element element = node; | 167 Element element = node; |
| 263 if (element.classes.contains('alert')) return; | 168 CssClassSet classes = element.classes; |
| 169 if (classes.contains('alert') || | |
| 170 classes.contains('dart-code-completion')) { | |
| 171 return; | |
| 172 } | |
| 264 } | 173 } |
| 265 | 174 |
| 266 var child = node.firstChild; | 175 var child = node.firstChild; |
| 267 while(child != null && !foundNode) { | 176 while(child != null && !foundNode) { |
| 268 walk4(child); | 177 walk4(child); |
| 269 child = child.nextNode; | 178 child = child.nextNode; |
| 270 } | 179 } |
| 271 } | 180 } |
| 272 walk4(inputPre); | 181 walk4(inputPre); |
| 273 | 182 |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 288 child.remove(); | 197 child.remove(); |
| 289 parent.insertBefore(child, element); | 198 parent.insertBefore(child, element); |
| 290 } | 199 } |
| 291 element.remove(); | 200 element.remove(); |
| 292 } | 201 } |
| 293 | 202 |
| 294 Decoration getDecoration(Token token) { | 203 Decoration getDecoration(Token token) { |
| 295 String tokenValue = token.value; | 204 String tokenValue = token.value; |
| 296 String tokenInfo = token.info.value; | 205 String tokenInfo = token.info.value; |
| 297 if (tokenInfo == 'string') return currentTheme.string; | 206 if (tokenInfo == 'string') return currentTheme.string; |
| 298 // if (tokenInfo == 'identifier') return identifier; | 207 if (tokenInfo == 'identifier') { |
| 208 seenIdentifiers.add(tokenValue); | |
| 209 return CodeCompletionDecoration.from(currentTheme.foreground); | |
| 210 } | |
| 299 if (tokenInfo == 'keyword') return currentTheme.keyword; | 211 if (tokenInfo == 'keyword') return currentTheme.keyword; |
| 300 if (tokenInfo == 'comment') return currentTheme.singleLineComment; | 212 if (tokenInfo == 'comment') return currentTheme.singleLineComment; |
| 301 if (tokenInfo == 'malformed input') { | 213 if (tokenInfo == 'malformed input') { |
| 302 isMalformedInput = true; | 214 isMalformedInput = true; |
| 303 return new DiagnosticDecoration('error', tokenValue); | 215 return new DiagnosticDecoration('error', tokenValue); |
| 304 } | 216 } |
| 305 return currentTheme.foreground; | 217 return currentTheme.foreground; |
| 306 } | 218 } |
| 307 | 219 |
| 308 diagnostic(text, tip) { | 220 diagnostic(text, tip) { |
| 309 if (text is String) { | 221 if (text is String) { |
| 310 text = new Text(text); | 222 text = new Text(text); |
| 311 } | 223 } |
| 312 return new AnchorElement() | 224 return new AnchorElement() |
| 313 ..classes.add('diagnostic') | 225 ..classes.add('diagnostic') |
| 314 ..append(text) | 226 ..append(text) |
| 315 ..append(tip); | 227 ..append(tip); |
| 316 } | 228 } |
| OLD | NEW |