Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2014, 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.interaction_manager; | |
| 6 | |
| 7 import 'dart:html'; | |
| 8 | |
| 9 import 'dart:convert' show | |
| 10 JSON; | |
| 11 | |
| 12 import 'dart:math' show | |
| 13 max, | |
| 14 min; | |
| 15 | |
| 16 import 'package:compiler/implementation/scanner/scannerlib.dart' | |
| 17 show | |
| 18 EOF_TOKEN, | |
| 19 StringScanner, | |
| 20 Token; | |
| 21 | |
| 22 import 'package:compiler/implementation/source_file.dart' show | |
| 23 StringSourceFile; | |
| 24 | |
| 25 import 'compilation.dart' show | |
| 26 scheduleCompilation; | |
| 27 | |
| 28 import 'ui.dart' show | |
| 29 currentTheme, | |
| 30 hackDiv, | |
| 31 inputPre, | |
| 32 observer, | |
| 33 outputDiv; | |
| 34 | |
| 35 import 'decoration.dart' show | |
| 36 CodeCompletionDecoration, | |
| 37 Decoration, | |
| 38 DiagnosticDecoration, | |
| 39 error, | |
| 40 info, | |
| 41 warning; | |
| 42 | |
| 43 import 'editor.dart' as editor; | |
| 44 | |
| 45 import 'mock.dart' as mock; | |
| 46 | |
| 47 import 'settings.dart' as settings; | |
| 48 | |
| 49 /** | |
| 50 * UI interaction manager for the entire application. | |
| 51 */ | |
| 52 abstract class InteractionManager { | |
| 53 // Design note: All UI interactions go through one instance of this | |
| 54 // class. This is by design. | |
| 55 // | |
| 56 // Simplicity in UI is in the eye of the beholder, not the implementor. Great | |
| 57 // 'natural UI' is usually achieved with substantial implementation | |
| 58 // complexity that doesn't modularise well and has nasty complicated state | |
| 59 // dependencies. | |
| 60 // | |
| 61 // In rare cases, some UI components can be independent of this state | |
| 62 // machine. For example, animation and auto-save loops. | |
| 63 | |
| 64 // Implementation note: The state machine is actually implemented by | |
| 65 // [InteractionContext], this class represents public event handlers. | |
| 66 | |
| 67 factory InteractionManager() => new InteractionContext(); | |
| 68 | |
| 69 InteractionManager.internal(); | |
| 70 | |
| 71 void onInput(Event event); | |
| 72 | |
| 73 void onKeyUp(KeyboardEvent event); | |
| 74 | |
| 75 void onMutation(List<MutationRecord> mutations, MutationObserver observer); | |
| 76 | |
| 77 void onSelectionChange(Event event); | |
| 78 } | |
| 79 | |
| 80 /** | |
| 81 * State machine for UI interactions. | |
| 82 */ | |
| 83 class InteractionContext extends InteractionManager { | |
| 84 InteractionState state; | |
| 85 | |
| 86 InteractionContext() | |
| 87 : super.internal() { | |
| 88 state = new InitialState(this); | |
| 89 } | |
| 90 | |
| 91 void onInput(Event event) => state.onInput(event); | |
| 92 | |
| 93 void onKeyUp(KeyboardEvent event) => state.onKeyUp(event); | |
| 94 | |
| 95 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { | |
| 96 return state.onMutation(mutations, observer); | |
| 97 } | |
| 98 | |
| 99 void onSelectionChange(Event event) => state.onSelectionChange(event); | |
| 100 } | |
| 101 | |
| 102 abstract class InteractionState implements InteractionManager { | |
| 103 void onStateChanged(InteractionState previous) { | |
| 104 print('State change ${previous.runtimeType} -> ${runtimeType}.'); | |
|
kasperl
2014/03/24 10:32:51
Debug printing (repeated)? Maybe introduce some ki
ahe
2014/03/24 15:20:23
There isn't much output from these print statement
| |
| 105 } | |
| 106 } | |
| 107 | |
| 108 class InitialState extends InteractionState { | |
| 109 final InteractionContext context; | |
| 110 bool requestCodeCompletion = false; | |
| 111 | |
| 112 InitialState(this.context); | |
| 113 | |
| 114 void set state(InteractionState state) { | |
| 115 InteractionState previous = context.state; | |
| 116 if (previous != state) { | |
| 117 context.state = state; | |
| 118 state.onStateChanged(previous); | |
| 119 } | |
| 120 } | |
| 121 | |
| 122 void onInput(Event event) { | |
| 123 state = new PendingInputState(context); | |
| 124 } | |
| 125 | |
| 126 void onKeyUp(KeyboardEvent event) { | |
| 127 if (computeHasModifier(event)) { | |
| 128 print('onKeyUp (modified)'); | |
| 129 onModifiedKeyUp(event); | |
| 130 } else { | |
| 131 print('onKeyUp (unmodified)'); | |
| 132 onUnmodifiedKeyUp(event); | |
| 133 } | |
| 134 } | |
| 135 | |
| 136 void onModifiedKeyUp(KeyboardEvent event) { | |
| 137 } | |
| 138 | |
| 139 void onUnmodifiedKeyUp(KeyboardEvent event) { | |
| 140 switch (event.keyCode) { | |
| 141 case KeyCode.ENTER: { | |
| 142 event.preventDefault(); | |
| 143 Selection selection = window.getSelection(); | |
| 144 if (selection.isCollapsed && selection.anchorNode is Text) { | |
| 145 Text text = selection.anchorNode; | |
| 146 int offset = selection.anchorOffset; | |
| 147 text.insertData(offset, '\n'); | |
| 148 selection.collapse(text, offset + 1); | |
| 149 } | |
| 150 break; | |
| 151 } | |
| 152 } | |
| 153 | |
| 154 // editor.scheduleRemoveCodeCompletion(); | |
| 155 | |
| 156 // This is a hack to get Safari (iOS) to send mutation events on | |
| 157 // contenteditable. | |
| 158 // TODO(ahe): Move to onInput? | |
| 159 var newDiv = new DivElement(); | |
| 160 hackDiv.replaceWith(newDiv); | |
| 161 hackDiv = newDiv; | |
| 162 } | |
| 163 | |
| 164 // TODO(ahe): This method should be cleaned up. It is too large. | |
| 165 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { | |
| 166 print('onMutation'); | |
| 167 | |
| 168 for (String query in ['a.diagnostic>span', | |
|
kasperl
2014/03/24 10:32:51
const [...]
ahe
2014/03/24 15:20:23
Done.
| |
| 169 '.dart-code-completion', | |
| 170 '.hazed-suggestion']) { | |
| 171 for (Element element in inputPre.querySelectorAll(query)) { | |
| 172 element.remove(); | |
| 173 } | |
| 174 } | |
| 175 | |
| 176 // Discard clean-up mutations. | |
| 177 observer.takeRecords(); | |
| 178 | |
| 179 Selection selection = window.getSelection(); | |
| 180 | |
| 181 while (!mutations.isEmpty) { | |
| 182 for (MutationRecord record in mutations) { | |
| 183 String type = record.type; | |
| 184 switch (type) { | |
| 185 | |
|
kasperl
2014/03/24 10:32:51
Remove newline.
ahe
2014/03/24 15:20:23
I've added it to aid navigation in Emacs.
| |
| 186 case 'characterData': | |
| 187 | |
|
kasperl
2014/03/24 10:32:51
Removew newline.
ahe
2014/03/24 15:20:23
Done.
| |
| 188 bool hasSelection = false; | |
| 189 int offset = selection.anchorOffset; | |
| 190 if (selection.isCollapsed && | |
| 191 selection.anchorNode == record.target) { | |
| 192 hasSelection = true; | |
| 193 } | |
| 194 var parent = record.target.parentNode; | |
| 195 if (parent != inputPre) { | |
| 196 editor.inlineChildren(parent); | |
| 197 } | |
| 198 if (hasSelection) { | |
| 199 selection.collapse(record.target, offset); | |
| 200 } | |
| 201 break; | |
| 202 | |
| 203 default: | |
| 204 if (!record.addedNodes.isEmpty) { | |
| 205 for (var node in record.addedNodes) { | |
| 206 | |
| 207 if (node.nodeType != Node.ELEMENT_NODE) continue; | |
| 208 | |
| 209 if (node is BRElement) { | |
| 210 if (selection.anchorNode != node) { | |
| 211 node.replaceWith(new Text('\n')); | |
| 212 } | |
| 213 } else { | |
| 214 var parent = node.parentNode; | |
| 215 if (parent == null) continue; | |
| 216 var nodes = new List.from(node.nodes); | |
| 217 var style = node.getComputedStyle(); | |
| 218 if (style.display != 'inline') { | |
| 219 var previous = node.previousNode; | |
| 220 if (previous is Text) { | |
| 221 previous.appendData('\n'); | |
| 222 } else { | |
| 223 parent.insertBefore(new Text('\n'), node); | |
| 224 } | |
| 225 } | |
| 226 for (Node child in nodes) { | |
| 227 child.remove(); | |
| 228 parent.insertBefore(child, node); | |
| 229 } | |
| 230 node.remove(); | |
| 231 } | |
| 232 } | |
| 233 } | |
| 234 } | |
| 235 } | |
| 236 mutations = observer.takeRecords(); | |
| 237 } | |
| 238 | |
| 239 if (!inputPre.nodes.isEmpty && inputPre.nodes.last is Text) { | |
| 240 Text text = inputPre.nodes.last; | |
| 241 if (!text.text.endsWith('\n')) { | |
| 242 text.appendData('\n'); | |
| 243 } | |
| 244 } | |
| 245 | |
| 246 int offset = 0; | |
| 247 int anchorOffset = 0; | |
| 248 bool hasSelection = false; | |
| 249 Node anchorNode = selection.anchorNode; | |
| 250 // TODO(ahe): Try to share walk4 methods. | |
| 251 void walk4(Node node) { | |
| 252 // TODO(ahe): Use TreeWalker when that is exposed. | |
| 253 // function textNodesUnder(root){ | |
| 254 // var n, a=[], walk=document.createTreeWalker( | |
| 255 // root,NodeFilter.SHOW_TEXT,null,false); | |
| 256 // while(n=walk.nextNode()) a.push(n); | |
| 257 // return a; | |
| 258 // } | |
| 259 int type = node.nodeType; | |
| 260 if (type == Node.TEXT_NODE || type == Node.CDATA_SECTION_NODE) { | |
| 261 CharacterData text = node; | |
| 262 if (anchorNode == node) { | |
| 263 hasSelection = true; | |
| 264 anchorOffset = selection.anchorOffset + offset; | |
| 265 return; | |
| 266 } | |
| 267 offset += text.length; | |
| 268 } | |
| 269 | |
| 270 var child = node.firstChild; | |
| 271 while (child != null) { | |
| 272 walk4(child); | |
| 273 if (hasSelection) return; | |
| 274 child = child.nextNode; | |
| 275 } | |
| 276 } | |
| 277 if (selection.isCollapsed) { | |
| 278 walk4(inputPre); | |
| 279 } | |
| 280 | |
| 281 editor.currentSource = inputPre.text; | |
| 282 inputPre.nodes.clear(); | |
| 283 inputPre.appendText(editor.currentSource); | |
| 284 if (hasSelection) { | |
| 285 selection.collapse(inputPre.firstChild, anchorOffset); | |
| 286 } | |
| 287 | |
| 288 editor.isMalformedInput = false; | |
| 289 for (var n in new List.from(inputPre.nodes)) { | |
| 290 if (n is! Text) continue; | |
| 291 Text node = n; | |
| 292 String text = node.text; | |
| 293 | |
| 294 Token token = tokenize(text); | |
| 295 int offset = 0; | |
| 296 editor.seenIdentifiers = new Set<String>.from(mock.identifiers); | |
| 297 for (;token.kind != EOF_TOKEN; token = token.next) { | |
|
kasperl
2014/03/24 10:32:51
I'd put a space after ;.
ahe
2014/03/24 15:20:23
Done.
| |
| 298 Decoration decoration = editor.getDecoration(token); | |
| 299 if (decoration == null) continue; | |
| 300 bool hasSelection = false; | |
| 301 int selectionOffset = selection.anchorOffset; | |
| 302 | |
| 303 if (selection.isCollapsed && selection.anchorNode == node) { | |
| 304 hasSelection = true; | |
| 305 selectionOffset = selection.anchorOffset; | |
| 306 } | |
| 307 int splitPoint = token.charOffset - offset; | |
| 308 Text str = node.splitText(splitPoint); | |
| 309 Text after = str.splitText(token.charCount); | |
| 310 offset += splitPoint + token.charCount; | |
| 311 inputPre.insertBefore(after, node.nextNode); | |
| 312 inputPre.insertBefore(decoration.applyTo(str), after); | |
| 313 | |
| 314 if (hasSelection && selectionOffset > node.length) { | |
| 315 selectionOffset -= node.length; | |
| 316 if (selectionOffset > str.length) { | |
| 317 selectionOffset -= str.length; | |
| 318 selection.collapse(after, selectionOffset); | |
| 319 } else { | |
| 320 selection.collapse(str, selectionOffset); | |
| 321 } | |
| 322 } | |
| 323 node = after; | |
| 324 } | |
| 325 } | |
| 326 | |
| 327 window.localStorage['currentSource'] = editor.currentSource; | |
| 328 print('Saved source'); | |
| 329 | |
| 330 // Discard highlighting mutations. | |
| 331 observer.takeRecords(); | |
| 332 } | |
| 333 | |
| 334 void onSelectionChange(Event event) { | |
| 335 } | |
| 336 | |
| 337 void onStateChanged(InteractionState previous) { | |
| 338 super.onStateChanged(previous); | |
| 339 scheduleCompilation(); | |
| 340 } | |
| 341 } | |
| 342 | |
| 343 class PendingInputState extends InitialState { | |
| 344 PendingInputState(InteractionContext context) | |
| 345 : super(context); | |
| 346 | |
| 347 void onInput(Event event) { | |
| 348 // Do nothing. | |
| 349 } | |
| 350 | |
| 351 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { | |
| 352 super.onMutation(mutations, observer); | |
| 353 | |
| 354 InteractionState nextState = new InitialState(context); | |
| 355 if (settings.enableCodeCompletion.value) { | |
| 356 Element parent = editor.getElementAtSelection(); | |
| 357 Element ui; | |
| 358 if (parent != null) { | |
| 359 ui = parent.querySelector('.dart-code-completion'); | |
| 360 if (ui != null) { | |
| 361 nextState = new CodeCompletionState(context, parent, ui); | |
| 362 } | |
| 363 } | |
| 364 } | |
| 365 state = nextState; | |
| 366 } | |
| 367 } | |
| 368 | |
| 369 class CodeCompletionState extends InitialState { | |
| 370 final Element activeCompletion; | |
| 371 final Element ui; | |
| 372 int minWidth = 0; | |
| 373 DivElement staticResults; | |
| 374 SpanElement inline; | |
| 375 DivElement serverResults; | |
| 376 String inlineSuggestion; | |
| 377 | |
| 378 CodeCompletionState(InteractionContext context, | |
| 379 this.activeCompletion, | |
| 380 this.ui) | |
| 381 : super(context); | |
| 382 | |
| 383 void onInput(Event event) { | |
| 384 // Do nothing. | |
| 385 } | |
| 386 | |
| 387 void onModifiedKeyUp(KeyboardEvent event) { | |
| 388 // TODO(ahe): Handle DOWN (jump to server results). | |
| 389 } | |
| 390 | |
| 391 void onUnmodifiedKeyUp(KeyboardEvent event) { | |
| 392 switch (event.keyCode) { | |
| 393 case KeyCode.DOWN: | |
| 394 return moveDown(event); | |
| 395 | |
| 396 case KeyCode.UP: | |
| 397 return moveUp(event); | |
| 398 | |
| 399 case KeyCode.ESC: | |
| 400 event.preventDefault(); | |
| 401 return endCompletion(); | |
| 402 | |
| 403 case KeyCode.TAB: | |
| 404 case KeyCode.RIGHT: | |
| 405 case KeyCode.ENTER: | |
| 406 event.preventDefault(); | |
| 407 return endCompletion(acceptSuggestion: true); | |
| 408 } | |
| 409 } | |
| 410 | |
| 411 void moveDown(Event event) { | |
| 412 event.preventDefault(); | |
| 413 move(1); | |
| 414 } | |
| 415 | |
| 416 void moveUp(Event event) { | |
| 417 event.preventDefault(); | |
| 418 move(-1); | |
| 419 } | |
| 420 | |
| 421 void move(int direction) { | |
| 422 Element element = editor.moveActive(direction); | |
| 423 if (element == null) return; | |
| 424 var text = activeCompletion.firstChild; | |
| 425 String prefix = ""; | |
| 426 if (text is Text) prefix = text.data.trim(); | |
| 427 updateInlineSuggestion(prefix, element.text); | |
| 428 } | |
| 429 | |
| 430 void endCompletion({bool acceptSuggestion: false}) { | |
| 431 if (acceptSuggestion) { | |
| 432 suggestionAccepted(); | |
| 433 } | |
| 434 activeCompletion.classes.remove('active'); | |
| 435 inputPre.querySelectorAll('.hazed-suggestion').forEach((e) => e.remove()); | |
| 436 // The above changes create mutation records. This implicitly fire mutation | |
| 437 // events that result in saving the source code in local storage. | |
| 438 // TODO(ahe): Consider making this more explicit. | |
| 439 state = new InitialState(context); | |
| 440 } | |
| 441 | |
| 442 void suggestionAccepted() { | |
| 443 if (inlineSuggestion != null) { | |
| 444 Text text = new Text(inlineSuggestion); | |
| 445 activeCompletion.replaceWith(text); | |
| 446 window.getSelection().collapse(text, inlineSuggestion.length); | |
| 447 } | |
| 448 } | |
| 449 | |
| 450 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { | |
| 451 for (MutationRecord record in mutations) { | |
| 452 if (!activeCompletion.contains(record.target)) { | |
| 453 endCompletion(); | |
| 454 return super.onMutation(mutations, observer); | |
| 455 } | |
| 456 } | |
| 457 | |
| 458 var text = activeCompletion.firstChild; | |
| 459 if (text is! Text) return endCompletion(); | |
| 460 updateSuggestions(text.data.trim()); | |
| 461 } | |
| 462 | |
| 463 void onStateChanged(InteractionState previous) { | |
| 464 super.onStateChanged(previous); | |
| 465 displayCodeCompletion(); | |
| 466 } | |
| 467 | |
| 468 void displayCodeCompletion() { | |
| 469 Selection selection = window.getSelection(); | |
| 470 if (selection.anchorNode is! Text) { | |
| 471 return endCompletion(); | |
| 472 } | |
| 473 Text text = selection.anchorNode; | |
| 474 if (!activeCompletion.contains(text)) { | |
| 475 return endCompletion(); | |
| 476 } | |
| 477 | |
| 478 int anchorOffset = selection.anchorOffset; | |
| 479 | |
| 480 String prefix = text.data.substring(0, anchorOffset).trim(); | |
| 481 if (prefix.isEmpty) { | |
| 482 return endCompletion(); | |
| 483 } | |
| 484 | |
| 485 num height = activeCompletion.getBoundingClientRect().height; | |
| 486 activeCompletion.classes.add('active'); | |
| 487 ui.nodes.clear(); | |
| 488 | |
| 489 inline = new SpanElement() | |
| 490 ..classes.add('hazed-suggestion'); | |
| 491 Text rest = text.splitText(anchorOffset); | |
| 492 text.parentNode.insertBefore(inline, text.nextNode); | |
| 493 activeCompletion.parentNode.insertBefore( | |
| 494 rest, activeCompletion.nextNode); | |
| 495 | |
| 496 staticResults = new DivElement() | |
| 497 ..classes.addAll(['dart-static', 'dart-limited-height']); | |
| 498 serverResults = new DivElement() | |
| 499 ..style.display = 'none' | |
| 500 ..classes.add('dart-server'); | |
| 501 ui.nodes.addAll([staticResults, serverResults]); | |
| 502 ui.style.top = '${height}px'; | |
| 503 | |
| 504 staticResults.nodes.add(buildCompletionEntry(prefix)); | |
| 505 | |
| 506 updateSuggestions(prefix); | |
| 507 } | |
| 508 | |
| 509 void updateInlineSuggestion(String prefix, String suggestion) { | |
| 510 inlineSuggestion = suggestion; | |
| 511 | |
| 512 minWidth = max(minWidth, activeCompletion.getBoundingClientRect().width); | |
| 513 | |
| 514 activeCompletion.style | |
| 515 ..display = 'inline-block' | |
| 516 ..minWidth = '${minWidth}px'; | |
| 517 | |
| 518 inline | |
| 519 ..nodes.clear() | |
| 520 ..appendText(suggestion.substring(prefix.length)) | |
| 521 ..style.display = ''; | |
| 522 | |
| 523 observer.takeRecords(); // Discard mutations. | |
| 524 } | |
| 525 | |
| 526 void updateSuggestions(String prefix) { | |
| 527 if (prefix.isEmpty) { | |
| 528 return endCompletion(); | |
| 529 } | |
| 530 | |
| 531 Token first = tokenize(prefix); | |
| 532 for (Token token = first; token.kind != EOF_TOKEN; token = token.next) { | |
| 533 String tokenInfo = token.info.value; | |
| 534 if (token != first || | |
| 535 tokenInfo != 'identifier' && | |
| 536 tokenInfo != 'keyword') { | |
| 537 return endCompletion(); | |
| 538 } | |
| 539 } | |
| 540 | |
| 541 var borderHeight = 2; // 1 pixel border top & bottom. | |
| 542 num height = ui.getBoundingClientRect().height - borderHeight; | |
| 543 ui.style.minHeight = '${height}px'; | |
| 544 | |
| 545 minWidth = | |
| 546 max(minWidth, activeCompletion.getBoundingClientRect().width); | |
| 547 | |
| 548 staticResults.nodes.clear(); | |
| 549 serverResults.nodes.clear(); | |
| 550 | |
| 551 if (inlineSuggestion != null && inlineSuggestion.startsWith(prefix)) { | |
| 552 inline | |
| 553 ..nodes.clear() | |
| 554 ..appendText(inlineSuggestion.substring(prefix.length)); | |
| 555 } | |
| 556 | |
| 557 List<String> results = editor.seenIdentifiers.where( | |
| 558 (String identifier) { | |
| 559 return identifier != prefix && identifier.startsWith(prefix); | |
| 560 }).toList(growable: false); | |
| 561 results.sort(); | |
| 562 if (results.isEmpty) results = <String>[prefix]; | |
| 563 | |
| 564 results.forEach((String completion) { | |
| 565 staticResults.nodes.add(buildCompletionEntry(completion)); | |
| 566 }); | |
| 567 | |
| 568 if (settings.enableDartMind) { | |
|
kasperl
2014/03/24 10:32:51
Maybe factor the DartMind support out in its own c
ahe
2014/03/24 15:20:23
Added TODO.
| |
| 569 String encodedArg0 = Uri.encodeComponent('"$prefix"'); | |
| 570 String mindQuery = | |
| 571 'http://dart-mind.appspot.com/rpc' | |
| 572 '?action=GetExportingPubCompletions' | |
| 573 '&arg0=$encodedArg0'; | |
| 574 try { | |
| 575 var serverWatch = new Stopwatch()..start(); | |
| 576 HttpRequest.getString(mindQuery).then((String responseText) { | |
| 577 serverWatch.stop(); | |
| 578 List<String> serverSuggestions = JSON.decode(responseText); | |
| 579 if (!serverSuggestions.isEmpty) { | |
| 580 updateInlineSuggestion(prefix, serverSuggestions.first); | |
| 581 } | |
| 582 for (int i = 1; i < serverSuggestions.length; i++) { | |
| 583 String completion = serverSuggestions[i]; | |
| 584 DivElement where = staticResults; | |
| 585 int index = results.indexOf(completion); | |
| 586 if (index != -1) { | |
| 587 List<Element> entries = | |
| 588 document.querySelectorAll('.dart-static>.dart-entry'); | |
| 589 entries[index].classes.add('doubleplusgood'); | |
| 590 } else { | |
| 591 if (results.length > 3) { | |
| 592 serverResults.style.display = 'block'; | |
| 593 where = serverResults; | |
| 594 } | |
| 595 Element entry = buildCompletionEntry(completion); | |
| 596 entry.classes.add('doubleplusgood'); | |
| 597 where.nodes.add(entry); | |
| 598 } | |
| 599 } | |
| 600 serverResults.appendHtml( | |
| 601 '<div>${serverWatch.elapsedMilliseconds}ms</div>'); | |
| 602 // Discard mutations. | |
| 603 observer.takeRecords(); | |
| 604 }).catchError((error, stack) { | |
| 605 window.console.dir(error); | |
| 606 window.console.error('$stack'); | |
| 607 }); | |
| 608 } catch (error, stack) { | |
| 609 window.console.dir(error); | |
| 610 window.console.error('$stack'); | |
| 611 } | |
| 612 } | |
| 613 // Discard mutations. | |
| 614 observer.takeRecords(); | |
| 615 } | |
| 616 | |
| 617 Element buildCompletionEntry(String completion) { | |
| 618 return new DivElement() | |
| 619 ..classes.add('dart-entry') | |
| 620 ..appendText(completion); | |
| 621 } | |
| 622 } | |
| 623 | |
| 624 Token tokenize(String text) { | |
| 625 var file = new StringSourceFile('', text); | |
| 626 return new StringScanner(file, includeComments: true).tokenize(); | |
| 627 } | |
| 628 | |
| 629 bool computeHasModifier(KeyboardEvent event) { | |
| 630 return | |
| 631 event.getModifierState("Alt") || | |
| 632 event.getModifierState("AltGraph") || | |
| 633 event.getModifierState("CapsLock") || | |
| 634 event.getModifierState("Control") || | |
| 635 event.getModifierState("Fn") || | |
| 636 event.getModifierState("Meta") || | |
| 637 event.getModifierState("NumLock") || | |
| 638 event.getModifierState("ScrollLock") || | |
| 639 event.getModifierState("Scroll") || | |
| 640 event.getModifierState("Win") || | |
| 641 event.getModifierState("Shift") || | |
| 642 event.getModifierState("SymbolLock") || | |
| 643 event.getModifierState("OS"); | |
| 644 } | |
| OLD | NEW |