| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library trydart.editor; | |
| 6 | |
| 7 import 'dart:html'; | |
| 8 | |
| 9 import 'package:compiler/src/scanner/string_scanner.dart' show | |
| 10 StringScanner; | |
| 11 | |
| 12 import 'package:compiler/src/tokens/token.dart' show | |
| 13 ErrorToken, | |
| 14 Token; | |
| 15 | |
| 16 import 'package:compiler/src/tokens/token_constants.dart' show | |
| 17 EOF_TOKEN; | |
| 18 | |
| 19 import 'ui.dart' show | |
| 20 currentTheme, | |
| 21 hackDiv, | |
| 22 interaction, | |
| 23 mainEditorPane, | |
| 24 observer, | |
| 25 outputDiv; | |
| 26 | |
| 27 import 'decoration.dart' show | |
| 28 CodeCompletionDecoration, | |
| 29 Decoration, | |
| 30 DiagnosticDecoration, | |
| 31 error, | |
| 32 info, | |
| 33 warning; | |
| 34 | |
| 35 import 'selection.dart' show | |
| 36 isCollapsed; | |
| 37 | |
| 38 import 'shadow_root.dart' show | |
| 39 getShadowRoot; | |
| 40 | |
| 41 import 'settings.dart' as settings; | |
| 42 | |
| 43 const String INDENT = '\u{a0}\u{a0}'; | |
| 44 | |
| 45 Set<String> seenIdentifiers; | |
| 46 | |
| 47 Element moveActive(int distance, Node ui) { | |
| 48 var /* ShadowRoot or Element */ root = getShadowRoot(ui); | |
| 49 List<Element> entries = root.querySelectorAll('.dart-static>.dart-entry'); | |
| 50 int activeIndex = -1; | |
| 51 for (var i = 0; i < entries.length; i++) { | |
| 52 if (entries[i].classes.contains('activeEntry')) { | |
| 53 activeIndex = i; | |
| 54 break; | |
| 55 } | |
| 56 } | |
| 57 int newIndex = activeIndex + distance; | |
| 58 Element currentEntry; | |
| 59 if (0 <= newIndex && newIndex < entries.length) { | |
| 60 currentEntry = entries[newIndex]; | |
| 61 } | |
| 62 if (currentEntry == null) return null; | |
| 63 if (0 <= newIndex && activeIndex != -1) { | |
| 64 entries[activeIndex].classes.remove('activeEntry'); | |
| 65 } | |
| 66 Element staticNode = root.querySelector('.dart-static'); | |
| 67 String visibility = computeVisibility(currentEntry, staticNode); | |
| 68 print(visibility); | |
| 69 var serverResults = root.querySelectorAll('.dart-server>.dart-entry'); | |
| 70 var serverResultCount = serverResults.length; | |
| 71 if (serverResultCount > 0) { | |
| 72 switch (visibility) { | |
| 73 case obscured: | |
| 74 case hidden: { | |
| 75 Rectangle cr = currentEntry.getBoundingClientRect(); | |
| 76 Rectangle sr = staticNode.getBoundingClientRect(); | |
| 77 Element entry = serverResults[0]; | |
| 78 entry.remove(); | |
| 79 currentEntry.parentNode.insertBefore(entry, currentEntry); | |
| 80 currentEntry = entry; | |
| 81 serverResultCount--; | |
| 82 | |
| 83 staticNode.style.maxHeight = '${sr.boundingBox(cr).height}px'; | |
| 84 } | |
| 85 } | |
| 86 } else { | |
| 87 currentEntry.scrollIntoView(); | |
| 88 } | |
| 89 if (serverResultCount == 0) { | |
| 90 root.querySelector('.dart-server').style.display = 'none'; | |
| 91 } | |
| 92 if (currentEntry != null) { | |
| 93 currentEntry.classes.add('activeEntry'); | |
| 94 } | |
| 95 // Discard mutations. | |
| 96 observer.takeRecords(); | |
| 97 return currentEntry; | |
| 98 } | |
| 99 | |
| 100 const visible = 'visible'; | |
| 101 const obscured = 'obscured'; | |
| 102 const hidden = 'hidden'; | |
| 103 | |
| 104 String computeVisibility(Element node, [Element parent]) { | |
| 105 Rectangle nr = node.getBoundingClientRect(); | |
| 106 if (parent == null) parent = node.parentNode; | |
| 107 Rectangle pr = parent.getBoundingClientRect(); | |
| 108 | |
| 109 if (pr.containsRectangle(nr)) return visible; | |
| 110 | |
| 111 if (pr.intersects(nr)) return obscured; | |
| 112 | |
| 113 return hidden; | |
| 114 } | |
| 115 | |
| 116 var activeCompletion; | |
| 117 num minSuggestionWidth = 0; | |
| 118 | |
| 119 /// Returns the [Element] which encloses the current collapsed selection, if it | |
| 120 /// exists. | |
| 121 Element getElementAtSelection() { | |
| 122 Selection selection = window.getSelection(); | |
| 123 if (!isCollapsed(selection)) return null; | |
| 124 var anchorNode = selection.anchorNode; | |
| 125 if (!mainEditorPane.contains(anchorNode)) return null; | |
| 126 if (mainEditorPane == anchorNode) return null; | |
| 127 int type = anchorNode.nodeType; | |
| 128 if (type != Node.TEXT_NODE) return null; | |
| 129 Text text = anchorNode; | |
| 130 var parent = text.parent; | |
| 131 if (parent is! Element) return null; | |
| 132 if (mainEditorPane == parent) return null; | |
| 133 return parent; | |
| 134 } | |
| 135 | |
| 136 bool isMalformedInput = false; | |
| 137 | |
| 138 addDiagnostic(String kind, String message, int begin, int end) { | |
| 139 observer.disconnect(); | |
| 140 Selection selection = window.getSelection(); | |
| 141 int offset = 0; | |
| 142 int anchorOffset = 0; | |
| 143 bool hasSelection = false; | |
| 144 Node anchorNode = selection.anchorNode; | |
| 145 bool foundNode = false; | |
| 146 void walk4(Node node) { | |
| 147 // TODO(ahe): Use TreeWalker when that is exposed. | |
| 148 int type = node.nodeType; | |
| 149 if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE) { | |
| 150 CharacterData cdata = node; | |
| 151 // print('walking: ${node.data}'); | |
| 152 if (anchorNode == node) { | |
| 153 hasSelection = true; | |
| 154 anchorOffset = selection.anchorOffset + offset; | |
| 155 } | |
| 156 int newOffset = offset + cdata.length; | |
| 157 if (offset <= begin && begin < newOffset) { | |
| 158 hasSelection = node == anchorNode; | |
| 159 anchorOffset = selection.anchorOffset; | |
| 160 var alert; | |
| 161 if (kind == 'error') { | |
| 162 alert = error(message); | |
| 163 } else if (kind == 'warning') { | |
| 164 alert = warning(message); | |
| 165 } else { | |
| 166 alert = info(message); | |
| 167 } | |
| 168 Element parent = node.parentNode; | |
| 169 if (parent.classes.contains("diagnostic") && | |
| 170 !interaction.oldDiagnostics.contains(parent)) { | |
| 171 Element other = parent.firstChild; | |
| 172 other.remove(); | |
| 173 SpanElement wrapper = new SpanElement() | |
| 174 ..classes.add('diagnostic') | |
| 175 ..style.fontWeight = 'normal'; | |
| 176 | |
| 177 var root = getShadowRoot(wrapper); | |
| 178 if (root is ShadowRoot) { | |
| 179 // When https://code.google.com/p/chromium/issues/detail?id=313458 | |
| 180 // is fixed: | |
| 181 // var link = new LinkElement() | |
| 182 // ..rel = "stylesheet" | |
| 183 // ..type = "text/css" | |
| 184 // ..href = "dartlang-style.css"; | |
| 185 // root.append(link); | |
| 186 root.append( | |
| 187 new StyleElement()..text = '@import url(dartlang-style.css)'); | |
| 188 } | |
| 189 root | |
| 190 ..append(other) | |
| 191 ..append(alert); | |
| 192 other.style.display = 'block'; | |
| 193 alert.style.display = 'block'; | |
| 194 parent.append(wrapper); | |
| 195 } else { | |
| 196 if (interaction.oldDiagnostics.contains(parent)) { | |
| 197 node.remove(); | |
| 198 parent.replaceWith(node); | |
| 199 } | |
| 200 Node marker = new Text(""); | |
| 201 node.replaceWith(marker); | |
| 202 // TODO(ahe): Don't highlight everything in the node. Find the | |
| 203 // relevant token (works for now as we create a node for each token, | |
| 204 // which is probably not great for performance). | |
| 205 marker.replaceWith(diagnostic(node, alert)); | |
| 206 if (hasSelection) { | |
| 207 selection.collapse(node, anchorOffset); | |
| 208 } | |
| 209 } | |
| 210 foundNode = true; | |
| 211 return; | |
| 212 } | |
| 213 offset = newOffset; | |
| 214 } else if (type == Node.ELEMENT_NODE) { | |
| 215 Element element = node; | |
| 216 CssClassSet classes = element.classes; | |
| 217 if (classes.contains('alert') || | |
| 218 classes.contains('dart-code-completion')) { | |
| 219 return; | |
| 220 } | |
| 221 } | |
| 222 | |
| 223 var child = node.firstChild; | |
| 224 while(child != null && !foundNode) { | |
| 225 walk4(child); | |
| 226 child = child.nextNode; | |
| 227 } | |
| 228 } | |
| 229 walk4(mainEditorPane); | |
| 230 | |
| 231 if (!foundNode) { | |
| 232 outputDiv.appendText('$message\n'); | |
| 233 } | |
| 234 | |
| 235 observer.takeRecords(); | |
| 236 observer.observe( | |
| 237 mainEditorPane, childList: true, characterData: true, subtree: true); | |
| 238 } | |
| 239 | |
| 240 Decoration getDecoration(Token token) { | |
| 241 if (token is ErrorToken) { | |
| 242 // TODO(ahe): Remove side effects from this method. It only leads to | |
| 243 // confusion. | |
| 244 isMalformedInput = true; | |
| 245 return new DiagnosticDecoration('error', token.assertionMessage); | |
| 246 } | |
| 247 String tokenValue = token.value; | |
| 248 String tokenInfo = token.info.value; | |
| 249 if (tokenInfo == 'string') return currentTheme.string; | |
| 250 if (tokenInfo == 'identifier') { | |
| 251 seenIdentifiers.add(tokenValue); | |
| 252 Decoration decoration = currentTheme.foreground; | |
| 253 if (settings.enableCodeCompletion.value) { | |
| 254 decoration = CodeCompletionDecoration.from(decoration); | |
| 255 } | |
| 256 return decoration; | |
| 257 } | |
| 258 if (tokenInfo == 'keyword') return currentTheme.keyword; | |
| 259 if (tokenInfo == 'comment') return currentTheme.singleLineComment; | |
| 260 if (tokenInfo == 'malformed input') { | |
| 261 // TODO(ahe): Remove side effects from this method. It only leads to | |
| 262 // confusion. | |
| 263 isMalformedInput = true; | |
| 264 return new DiagnosticDecoration('error', tokenValue); | |
| 265 } | |
| 266 return currentTheme.foreground; | |
| 267 } | |
| 268 | |
| 269 diagnostic(content, tip) { | |
| 270 if (content is String) { | |
| 271 content = new Text(content); | |
| 272 } | |
| 273 if (content is! List) { | |
| 274 content = [content]; | |
| 275 } | |
| 276 return new AnchorElement() | |
| 277 ..classes.add('diagnostic') | |
| 278 ..append(tip) // Should be first for better Firefox editing. | |
| 279 ..nodes.addAll(content); | |
| 280 } | |
| OLD | NEW |