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 'dart:async' show | |
| 10 Timer; | |
| 11 | |
| 12 import 'dart:convert' show | |
| 13 JSON; | |
| 14 | |
| 9 import '../../../sdk/lib/_internal/compiler/implementation/scanner/scannerlib.da rt' | 15 import '../../../sdk/lib/_internal/compiler/implementation/scanner/scannerlib.da rt' |
| 10 show | 16 show |
| 11 EOF_TOKEN, | 17 EOF_TOKEN, |
| 12 StringScanner, | 18 StringScanner, |
| 13 Token; | 19 Token; |
| 14 | 20 |
| 15 import '../../../sdk/lib/_internal/compiler/implementation/source_file.dart' sho w | 21 import '../../../sdk/lib/_internal/compiler/implementation/source_file.dart' sho w |
| 16 StringSourceFile; | 22 StringSourceFile; |
| 17 | 23 |
| 18 import 'compilation.dart' show | 24 import 'compilation.dart' show |
| 19 scheduleCompilation; | 25 scheduleCompilation; |
| 20 | 26 |
| 21 import 'ui.dart' show | 27 import 'ui.dart' show |
| 28 applyingSettings, | |
| 22 currentTheme, | 29 currentTheme, |
| 23 hackDiv, | 30 hackDiv, |
| 24 inputPre, | 31 inputPre, |
| 25 observer, | 32 observer, |
| 26 outputDiv; | 33 outputDiv; |
| 27 | 34 |
| 28 import 'decoration.dart' show | 35 import 'decoration.dart' show |
| 36 CodeCompletionDecoration, | |
| 29 Decoration, | 37 Decoration, |
| 30 DiagnosticDecoration, | 38 DiagnosticDecoration, |
| 31 error, | 39 error, |
| 32 info, | 40 info, |
| 33 warning; | 41 warning; |
| 34 | 42 |
| 35 const String INDENT = '\u{a0}\u{a0}'; | 43 const String INDENT = '\u{a0}\u{a0}'; |
| 36 | 44 |
| 45 Set<String> seenIdentifiers; | |
| 46 | |
| 37 onKeyUp(KeyboardEvent e) { | 47 onKeyUp(KeyboardEvent e) { |
| 48 scheduleCodeCompletion(); | |
| 49 | |
| 50 if (e.keyCode == 40 && activeCompletion != null) { | |
| 51 e.preventDefault(); | |
| 52 print('${e.keyCode}: ${e}'); | |
| 53 return; | |
| 54 } | |
| 55 | |
| 38 if (e.keyCode == 13) { | 56 if (e.keyCode == 13) { |
| 39 e.preventDefault(); | 57 e.preventDefault(); |
| 40 Selection selection = window.getSelection(); | 58 Selection selection = window.getSelection(); |
| 41 if (selection.isCollapsed && selection.anchorNode is Text) { | 59 if (selection.isCollapsed && selection.anchorNode is Text) { |
| 42 Text text = selection.anchorNode; | 60 Text text = selection.anchorNode; |
| 43 int offset = selection.anchorOffset; | 61 int offset = selection.anchorOffset; |
| 44 text.insertData(offset, '\n'); | 62 text.insertData(offset, '\n'); |
| 45 selection.collapse(text, offset + 1); | 63 selection.collapse(text, offset + 1); |
| 46 } | 64 } |
| 47 } | 65 } |
| 48 // This is a hack to get Safari to send mutation events on contenteditable. | 66 // This is a hack to get Safari to send mutation events on contenteditable. |
| 49 var newDiv = new DivElement(); | 67 var newDiv = new DivElement(); |
| 50 hackDiv.replaceWith(newDiv); | 68 hackDiv.replaceWith(newDiv); |
| 51 hackDiv = newDiv; | 69 hackDiv = newDiv; |
| 52 } | 70 } |
| 53 | 71 |
| 72 Timer codeCompletionTimer; | |
| 73 | |
| 74 void scheduleCodeCompletion() { | |
| 75 if (applyingSettings) return; | |
| 76 if (codeCompletionTimer != null) { | |
| 77 codeCompletionTimer.cancel(); | |
| 78 codeCompletionTimer = null; | |
| 79 } | |
| 80 codeCompletionTimer = | |
| 81 new Timer(const Duration(milliseconds: 1), displayCodeCompletion); | |
| 82 } | |
| 83 | |
| 84 var activeCompletion; | |
| 85 | |
| 86 void displayCodeCompletion() { | |
| 87 if (activeCompletion != null) { | |
| 88 activeCompletion.classes.remove('active'); | |
| 89 activeCompletion = null; | |
| 90 } | |
| 91 Selection selection = window.getSelection(); | |
| 92 if (!selection.isCollapsed) return; | |
| 93 var anchorNode = selection.anchorNode; | |
| 94 if (!inputPre.contains(anchorNode)) return; | |
| 95 int anchorOffset = selection.anchorOffset; | |
| 96 int type = anchorNode.nodeType; | |
| 97 if (type != Node.TEXT_NODE) return; | |
| 98 Text text = anchorNode; | |
| 99 var parent = text.parent; | |
| 100 if (parent is! Element) return; | |
| 101 parent.classes.add('active'); | |
| 102 var ui = parent.query('.dart-code-completion'); | |
| 103 if (ui == null) return; | |
| 104 activeCompletion = parent; | |
| 105 observer.disconnect(); | |
| 106 ui.nodes.clear(); | |
| 107 String prefix = text.data.substring(0, anchorOffset).trim(); | |
| 108 if (!prefix.isEmpty) { | |
| 109 String encodedArg0 = Uri.encodeComponent('"$prefix"'); | |
| 110 String mindQuery = | |
| 111 'http://dart-mind.appspot.com/rpc' | |
| 112 '?action=GetExportingPubCompletions' | |
| 113 '&arg0=$encodedArg0'; | |
| 114 HttpRequest request = new HttpRequest(); | |
| 115 request.open('GET', mindQuery, async: false); | |
| 116 try { | |
| 117 List<String> response = <String>[]; | |
| 118 var sw = new Stopwatch(); | |
| 119 if (false) { | |
| 120 sw.start(); | |
| 121 request.send(); | |
| 122 sw.stop(); | |
| 123 if (request.status == 200) { | |
| 124 response = JSON.decode(request.responseText); | |
| 125 } | |
| 126 } else { | |
| 127 sw.start(); | |
| 128 response = | |
| 129 seenIdentifiers.where( | |
| 130 (String identifier) => identifier != prefix && identifier.starts With(prefix)) | |
|
kasperl
2014/03/13 07:39:58
Long line.
| |
| 131 .toList(growable: false) | |
| 132 ..sort(); | |
|
kasperl
2014/03/13 07:39:58
I think I'd prefer to not use .. for this. It's a
| |
| 133 sw.stop(); | |
| 134 } | |
| 135 response.forEach((String completion) { | |
| 136 ui.appendHtml('$completion<br>'); | |
| 137 }); | |
| 138 ui.appendHtml('${sw.elapsedMilliseconds}ms<br>'); | |
| 139 } catch (e) { | |
| 140 window.console.dir(e); | |
| 141 } | |
| 142 } | |
| 143 observer.observe(inputPre, childList: true, characterData: true, subtree: true ); | |
|
kasperl
2014/03/13 07:39:58
Long line.
| |
| 144 } | |
| 145 | |
| 54 bool isMalformedInput = false; | 146 bool isMalformedInput = false; |
| 55 String currentSource = ""; | 147 String currentSource = ""; |
| 56 | 148 |
| 57 // TODO(ahe): This method should be cleaned up. It is too large. | 149 // TODO(ahe): This method should be cleaned up. It is too large. |
| 58 onMutation(List<MutationRecord> mutations, MutationObserver observer) { | 150 onMutation(List<MutationRecord> mutations, MutationObserver observer) { |
| 59 scheduleCompilation(); | 151 // scheduleCompilation(); |
| 60 | 152 |
| 61 for (Element element in inputPre.queryAll('a[class="diagnostic"]>span')) { | 153 for (Element element in inputPre.queryAll('a.diagnostic>span')) { |
| 62 element.remove(); | 154 element.remove(); |
| 63 } | 155 } |
| 156 for (Element element in inputPre.queryAll('.dart-code-completion')) { | |
| 157 element.remove(); | |
| 158 } | |
| 159 | |
| 64 // Discard clean-up mutations. | 160 // Discard clean-up mutations. |
| 65 observer.takeRecords(); | 161 observer.takeRecords(); |
| 66 | 162 |
| 67 Selection selection = window.getSelection(); | 163 Selection selection = window.getSelection(); |
| 68 | 164 |
| 69 while (!mutations.isEmpty) { | 165 while (!mutations.isEmpty) { |
| 70 for (MutationRecord record in mutations) { | 166 for (MutationRecord record in mutations) { |
| 71 String type = record.type; | 167 String type = record.type; |
| 72 switch (type) { | 168 switch (type) { |
| 73 | 169 |
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 174 | 270 |
| 175 isMalformedInput = false; | 271 isMalformedInput = false; |
| 176 for (var n in new List.from(inputPre.nodes)) { | 272 for (var n in new List.from(inputPre.nodes)) { |
| 177 if (n is! Text) continue; | 273 if (n is! Text) continue; |
| 178 Text node = n; | 274 Text node = n; |
| 179 String text = node.text; | 275 String text = node.text; |
| 180 | 276 |
| 181 Token token = new StringScanner( | 277 Token token = new StringScanner( |
| 182 new StringSourceFile('', text), includeComments: true).tokenize(); | 278 new StringSourceFile('', text), includeComments: true).tokenize(); |
| 183 int offset = 0; | 279 int offset = 0; |
| 280 seenIdentifiers = new Set<String>(); | |
| 184 for (;token.kind != EOF_TOKEN; token = token.next) { | 281 for (;token.kind != EOF_TOKEN; token = token.next) { |
| 185 Decoration decoration = getDecoration(token); | 282 Decoration decoration = getDecoration(token); |
| 186 if (decoration == null) continue; | 283 if (decoration == null) continue; |
| 187 bool hasSelection = false; | 284 bool hasSelection = false; |
| 188 int selectionOffset = selection.anchorOffset; | 285 int selectionOffset = selection.anchorOffset; |
| 189 | 286 |
| 190 if (selection.isCollapsed && selection.anchorNode == node) { | 287 if (selection.isCollapsed && selection.anchorNode == node) { |
| 191 hasSelection = true; | 288 hasSelection = true; |
| 192 selectionOffset = selection.anchorOffset; | 289 selectionOffset = selection.anchorOffset; |
| 193 } | 290 } |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 206 } else { | 303 } else { |
| 207 selection.collapse(str, selectionOffset); | 304 selection.collapse(str, selectionOffset); |
| 208 } | 305 } |
| 209 } | 306 } |
| 210 node = after; | 307 node = after; |
| 211 } | 308 } |
| 212 } | 309 } |
| 213 | 310 |
| 214 window.localStorage['currentSource'] = currentSource; | 311 window.localStorage['currentSource'] = currentSource; |
| 215 | 312 |
| 313 if (activeCompletion != null) { | |
| 314 if (codeCompletionTimer != null) { | |
| 315 codeCompletionTimer.cancel(); | |
| 316 codeCompletionTimer = null; | |
| 317 } | |
| 318 displayCodeCompletion(); | |
| 319 } | |
| 320 | |
| 216 // Discard highlighting mutations. | 321 // Discard highlighting mutations. |
| 217 observer.takeRecords(); | 322 observer.takeRecords(); |
| 218 } | 323 } |
| 219 | 324 |
| 220 addDiagnostic(String kind, String message, int begin, int end) { | 325 addDiagnostic(String kind, String message, int begin, int end) { |
| 221 observer.disconnect(); | 326 observer.disconnect(); |
| 222 Selection selection = window.getSelection(); | 327 Selection selection = window.getSelection(); |
| 223 int offset = 0; | 328 int offset = 0; |
| 224 int anchorOffset = 0; | 329 int anchorOffset = 0; |
| 225 bool hasSelection = false; | 330 bool hasSelection = false; |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 288 child.remove(); | 393 child.remove(); |
| 289 parent.insertBefore(child, element); | 394 parent.insertBefore(child, element); |
| 290 } | 395 } |
| 291 element.remove(); | 396 element.remove(); |
| 292 } | 397 } |
| 293 | 398 |
| 294 Decoration getDecoration(Token token) { | 399 Decoration getDecoration(Token token) { |
| 295 String tokenValue = token.value; | 400 String tokenValue = token.value; |
| 296 String tokenInfo = token.info.value; | 401 String tokenInfo = token.info.value; |
| 297 if (tokenInfo == 'string') return currentTheme.string; | 402 if (tokenInfo == 'string') return currentTheme.string; |
| 298 // if (tokenInfo == 'identifier') return identifier; | 403 if (tokenInfo == 'identifier') { |
| 404 seenIdentifiers.add(tokenValue); | |
| 405 | |
| 406 print(seenIdentifiers); | |
| 407 return CodeCompletionDecoration.from(currentTheme.foreground); | |
| 408 } | |
| 299 if (tokenInfo == 'keyword') return currentTheme.keyword; | 409 if (tokenInfo == 'keyword') return currentTheme.keyword; |
| 300 if (tokenInfo == 'comment') return currentTheme.singleLineComment; | 410 if (tokenInfo == 'comment') return currentTheme.singleLineComment; |
| 301 if (tokenInfo == 'malformed input') { | 411 if (tokenInfo == 'malformed input') { |
| 302 isMalformedInput = true; | 412 isMalformedInput = true; |
| 303 return new DiagnosticDecoration('error', tokenValue); | 413 return new DiagnosticDecoration('error', tokenValue); |
| 304 } | 414 } |
| 305 return currentTheme.foreground; | 415 return currentTheme.foreground; |
| 306 } | 416 } |
| 307 | 417 |
| 308 diagnostic(text, tip) { | 418 diagnostic(text, tip) { |
| 309 if (text is String) { | 419 if (text is String) { |
| 310 text = new Text(text); | 420 text = new Text(text); |
| 311 } | 421 } |
| 312 return new AnchorElement() | 422 return new AnchorElement() |
| 313 ..classes.add('diagnostic') | 423 ..classes.add('diagnostic') |
| 314 ..append(text) | 424 ..append(text) |
| 315 ..append(tip); | 425 ..append(tip); |
| 316 } | 426 } |
| OLD | NEW |