| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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.interaction_manager; | 5 library trydart.interaction_manager; |
| 6 | 6 |
| 7 import 'dart:html'; | 7 import 'dart:html'; |
| 8 | 8 |
| 9 import 'dart:convert' show | 9 import 'dart:convert' show |
| 10 JSON; | 10 JSON; |
| (...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 229 : super.internal() { | 229 : super.internal() { |
| 230 state = new InitialState(this); | 230 state = new InitialState(this); |
| 231 heartbeat = new Timer.periodic(HEARTBEAT_INTERVAL, onHeartbeat); | 231 heartbeat = new Timer.periodic(HEARTBEAT_INTERVAL, onHeartbeat); |
| 232 } | 232 } |
| 233 | 233 |
| 234 void onInput(Event event) => state.onInput(event); | 234 void onInput(Event event) => state.onInput(event); |
| 235 | 235 |
| 236 void onKeyUp(KeyboardEvent event) => state.onKeyUp(event); | 236 void onKeyUp(KeyboardEvent event) => state.onKeyUp(event); |
| 237 | 237 |
| 238 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { | 238 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { |
| 239 workAroundFirefoxBug(); |
| 239 try { | 240 try { |
| 240 try { | 241 try { |
| 241 return state.onMutation(mutations, observer); | 242 return state.onMutation(mutations, observer); |
| 242 } finally { | 243 } finally { |
| 243 // Discard any mutations during the observer, as these can lead to | 244 // Discard any mutations during the observer, as these can lead to |
| 244 // infinite loop. | 245 // infinite loop. |
| 245 observer.takeRecords(); | 246 observer.takeRecords(); |
| 246 } | 247 } |
| 247 } catch (error, stackTrace) { | 248 } catch (error, stackTrace) { |
| 248 try { | 249 try { |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 301 InteractionContext get context; | 302 InteractionContext get context; |
| 302 | 303 |
| 303 // TODO(ahe): Remove this. | 304 // TODO(ahe): Remove this. |
| 304 Set<AnchorElement> get oldDiagnostics { | 305 Set<AnchorElement> get oldDiagnostics { |
| 305 throw 'Use context.oldDiagnostics instead'; | 306 throw 'Use context.oldDiagnostics instead'; |
| 306 } | 307 } |
| 307 | 308 |
| 308 void set state(InteractionState newState); | 309 void set state(InteractionState newState); |
| 309 | 310 |
| 310 void onStateChanged(InteractionState previous) { | 311 void onStateChanged(InteractionState previous) { |
| 311 print('State change ${previous.runtimeType} -> ${runtimeType}.'); | |
| 312 } | 312 } |
| 313 | 313 |
| 314 void transitionToInitialState() { | 314 void transitionToInitialState() { |
| 315 state = new InitialState(context); | 315 state = new InitialState(context); |
| 316 } | 316 } |
| 317 } | 317 } |
| 318 | 318 |
| 319 class InitialState extends InteractionState { | 319 class InitialState extends InteractionState { |
| 320 final InteractionContext context; | 320 final InteractionContext context; |
| 321 bool requestCodeCompletion = false; | 321 bool requestCodeCompletion = false; |
| 322 | 322 |
| 323 InitialState(this.context); | 323 InitialState(this.context); |
| 324 | 324 |
| 325 void set state(InteractionState state) { | 325 void set state(InteractionState state) { |
| 326 InteractionState previous = context.state; | 326 InteractionState previous = context.state; |
| 327 if (previous != state) { | 327 if (previous != state) { |
| 328 context.state = state; | 328 context.state = state; |
| 329 state.onStateChanged(previous); | 329 state.onStateChanged(previous); |
| 330 } | 330 } |
| 331 } | 331 } |
| 332 | 332 |
| 333 void onInput(Event event) { | 333 void onInput(Event event) { |
| 334 state = new PendingInputState(context); | 334 state = new PendingInputState(context); |
| 335 } | 335 } |
| 336 | 336 |
| 337 void onKeyUp(KeyboardEvent event) { | 337 void onKeyUp(KeyboardEvent event) { |
| 338 if (computeHasModifier(event)) { | 338 if (computeHasModifier(event)) { |
| 339 print('onKeyUp (modified)'); | |
| 340 onModifiedKeyUp(event); | 339 onModifiedKeyUp(event); |
| 341 } else { | 340 } else { |
| 342 print('onKeyUp (unmodified)'); | |
| 343 onUnmodifiedKeyUp(event); | 341 onUnmodifiedKeyUp(event); |
| 344 } | 342 } |
| 345 } | 343 } |
| 346 | 344 |
| 347 void onModifiedKeyUp(KeyboardEvent event) { | 345 void onModifiedKeyUp(KeyboardEvent event) { |
| 348 if (event.getModifierState("Shift")) return onShiftedKeyUp(event); | 346 if (event.getModifierState("Shift")) return onShiftedKeyUp(event); |
| 349 switch (event.keyCode) { | 347 switch (event.keyCode) { |
| 350 case KeyCode.S: | 348 case KeyCode.S: |
| 351 // Disable Ctrl-S, Cmd-S, etc. We have observed users hitting these | 349 // Disable Ctrl-S, Cmd-S, etc. We have observed users hitting these |
| 352 // keys often when using Try Dart and getting frustrated. | 350 // keys often when using Try Dart and getting frustrated. |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 406 | 404 |
| 407 // This is a hack to get Safari (iOS) to send mutation events on | 405 // This is a hack to get Safari (iOS) to send mutation events on |
| 408 // contenteditable. | 406 // contenteditable. |
| 409 // TODO(ahe): Move to onInput? | 407 // TODO(ahe): Move to onInput? |
| 410 var newDiv = new DivElement(); | 408 var newDiv = new DivElement(); |
| 411 hackDiv.replaceWith(newDiv); | 409 hackDiv.replaceWith(newDiv); |
| 412 hackDiv = newDiv; | 410 hackDiv = newDiv; |
| 413 } | 411 } |
| 414 | 412 |
| 415 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { | 413 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { |
| 416 print('onMutation'); | |
| 417 | |
| 418 removeCodeCompletion(); | 414 removeCodeCompletion(); |
| 419 | 415 |
| 420 Selection selection = window.getSelection(); | 416 Selection selection = window.getSelection(); |
| 421 TrySelection trySelection = new TrySelection(mainEditorPane, selection); | 417 TrySelection trySelection = new TrySelection(mainEditorPane, selection); |
| 422 | 418 |
| 423 Set<Node> normalizedNodes = new Set<Node>(); | 419 Set<Node> normalizedNodes = new Set<Node>(); |
| 424 for (MutationRecord record in mutations) { | 420 for (MutationRecord record in mutations) { |
| 425 normalizeMutationRecord(record, trySelection, normalizedNodes); | 421 normalizeMutationRecord(record, trySelection, normalizedNodes); |
| 426 } | 422 } |
| 427 | 423 |
| (...skipping 21 matching lines...) Expand all Loading... |
| 449 for (String line in splitLines(currentText)) { | 445 for (String line in splitLines(currentText)) { |
| 450 List<Node> lineNodes = <Node>[]; | 446 List<Node> lineNodes = <Node>[]; |
| 451 state = tokenizeAndHighlight( | 447 state = tokenizeAndHighlight( |
| 452 line, state, offset, trySelection, lineNodes); | 448 line, state, offset, trySelection, lineNodes); |
| 453 offset += line.length; | 449 offset += line.length; |
| 454 nodes.add(makeLine(lineNodes, state)); | 450 nodes.add(makeLine(lineNodes, state)); |
| 455 } | 451 } |
| 456 | 452 |
| 457 node.parent.insertAllBefore(nodes, node); | 453 node.parent.insertAllBefore(nodes, node); |
| 458 node.remove(); | 454 node.remove(); |
| 459 trySelection.adjust(selection); | 455 if (mainEditorPane.contains(trySelection.anchorNode)) { |
| 456 // Sometimes the anchor node is removed by the above call. This has |
| 457 // only been observed in Firefox, and is hard to reproduce. |
| 458 trySelection.adjust(selection); |
| 459 } |
| 460 | 460 |
| 461 // TODO(ahe): We know almost exactly what has changed. It could be | 461 // TODO(ahe): We know almost exactly what has changed. It could be |
| 462 // more efficient to only communicate what changed. | 462 // more efficient to only communicate what changed. |
| 463 context.currentCompilationUnit.content = getText(mainEditorPane); | 463 context.currentCompilationUnit.content = getText(mainEditorPane); |
| 464 | 464 |
| 465 // Discard highlighting mutations. | 465 // Discard highlighting mutations. |
| 466 observer.takeRecords(); | 466 observer.takeRecords(); |
| 467 return; | 467 return; |
| 468 } | 468 } |
| 469 } | 469 } |
| (...skipping 729 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1199 } | 1199 } |
| 1200 if (!record.removedNodes.isEmpty) { | 1200 if (!record.removedNodes.isEmpty) { |
| 1201 var first = record.removedNodes.first; | 1201 var first = record.removedNodes.first; |
| 1202 var line = findLine(record.target); | 1202 var line = findLine(record.target); |
| 1203 | 1203 |
| 1204 if (first is Text && first.data=="\n" && line.nextNode != null) { | 1204 if (first is Text && first.data=="\n" && line.nextNode != null) { |
| 1205 normalizedNodes.add(line.nextNode); | 1205 normalizedNodes.add(line.nextNode); |
| 1206 } | 1206 } |
| 1207 normalizedNodes.add(line); | 1207 normalizedNodes.add(line); |
| 1208 } | 1208 } |
| 1209 if (record.type == "characterData") { | 1209 if (record.type == "characterData" && record.target.parent != null) { |
| 1210 // At least Firefox sends a "characterData" record whose target is the |
| 1211 // deleted text node. It also sends a record where "removedNodes" isn't |
| 1212 // empty whose target is the parent (which we are interested in). |
| 1210 normalizedNodes.add(findLine(record.target)); | 1213 normalizedNodes.add(findLine(record.target)); |
| 1211 } | 1214 } |
| 1212 } | 1215 } |
| 1213 | 1216 |
| 1214 // Finds the line of [node] (a parent node with CSS class 'lineNumber'). | 1217 // Finds the line of [node] (a parent node with CSS class 'lineNumber'). |
| 1215 // If no such parent exists, return mainEditorPane if it is a parent. | 1218 // If no such parent exists, return mainEditorPane if it is a parent. |
| 1216 // Otherwise return [node]. | 1219 // Otherwise return [node]. |
| 1217 Node findLine(Node node) { | 1220 Node findLine(Node node) { |
| 1218 for (Node n = node; n != null; n = n.parent) { | 1221 for (Node n = node; n != null; n = n.parent) { |
| 1219 if (n is Element && n.classes.contains('lineNumber')) return n; | 1222 if (n is Element && n.classes.contains('lineNumber')) return n; |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1252 bool isCompilerStageMarker(String message) { | 1255 bool isCompilerStageMarker(String message) { |
| 1253 return | 1256 return |
| 1254 message.startsWith('Package root is ') || | 1257 message.startsWith('Package root is ') || |
| 1255 message.startsWith('Compiling ') || | 1258 message.startsWith('Compiling ') || |
| 1256 message == "Resolving..." || | 1259 message == "Resolving..." || |
| 1257 message.startsWith('Resolved ') || | 1260 message.startsWith('Resolved ') || |
| 1258 message == "Inferring types..." || | 1261 message == "Inferring types..." || |
| 1259 message == "Compiling..." || | 1262 message == "Compiling..." || |
| 1260 message.startsWith('Compiled '); | 1263 message.startsWith('Compiled '); |
| 1261 } | 1264 } |
| 1265 |
| 1266 void workAroundFirefoxBug() { |
| 1267 Selection selection = window.getSelection(); |
| 1268 if (!isCollapsed(selection)) return; |
| 1269 Node node = selection.anchorNode; |
| 1270 int offset = selection.anchorOffset; |
| 1271 if (selection.anchorNode is Element && selection.anchorOffset != 0) { |
| 1272 // In some cases, Firefox reports the wrong anchorOffset (always seems to |
| 1273 // be 6) when anchorNode is an Element. Moving the cursor back and forth |
| 1274 // adjusts the anchorOffset. |
| 1275 // Safari can also reach this code, but the offset isn't wrong, just |
| 1276 // inconsistent. After moving the cursor back and forth, Safari will make |
| 1277 // the offset relative to a text node. |
| 1278 selection |
| 1279 ..modify('move', 'backward', 'character') |
| 1280 ..modify('move', 'forward', 'character'); |
| 1281 print('Selection adjusted $node@$offset -> ' |
| 1282 '${selection.anchorNode}@${selection.anchorOffset}.'); |
| 1283 } |
| 1284 } |
| OLD | NEW |