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 210 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
221 : super.internal() { | 221 : super.internal() { |
222 state = new InitialState(this); | 222 state = new InitialState(this); |
223 heartbeat = new Timer.periodic(HEARTBEAT_INTERVAL, onHeartbeat); | 223 heartbeat = new Timer.periodic(HEARTBEAT_INTERVAL, onHeartbeat); |
224 } | 224 } |
225 | 225 |
226 void onInput(Event event) => state.onInput(event); | 226 void onInput(Event event) => state.onInput(event); |
227 | 227 |
228 void onKeyUp(KeyboardEvent event) => state.onKeyUp(event); | 228 void onKeyUp(KeyboardEvent event) => state.onKeyUp(event); |
229 | 229 |
230 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { | 230 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { |
231 workAroundFirefoxBug(); | |
231 try { | 232 try { |
232 try { | 233 try { |
233 return state.onMutation(mutations, observer); | 234 return state.onMutation(mutations, observer); |
234 } finally { | 235 } finally { |
235 // Discard any mutations during the observer, as these can lead to | 236 // Discard any mutations during the observer, as these can lead to |
236 // infinite loop. | 237 // infinite loop. |
237 observer.takeRecords(); | 238 observer.takeRecords(); |
238 } | 239 } |
239 } catch (error, stackTrace) { | 240 } catch (error, stackTrace) { |
240 try { | 241 try { |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
298 InteractionContext get context; | 299 InteractionContext get context; |
299 | 300 |
300 // TODO(ahe): Remove this. | 301 // TODO(ahe): Remove this. |
301 Set<AnchorElement> get oldDiagnostics { | 302 Set<AnchorElement> get oldDiagnostics { |
302 throw 'Use context.oldDiagnostics instead'; | 303 throw 'Use context.oldDiagnostics instead'; |
303 } | 304 } |
304 | 305 |
305 void set state(InteractionState newState); | 306 void set state(InteractionState newState); |
306 | 307 |
307 void onStateChanged(InteractionState previous) { | 308 void onStateChanged(InteractionState previous) { |
308 print('State change ${previous.runtimeType} -> ${runtimeType}.'); | |
309 } | 309 } |
310 | 310 |
311 void transitionToInitialState() { | 311 void transitionToInitialState() { |
312 state = new InitialState(context); | 312 state = new InitialState(context); |
313 } | 313 } |
314 } | 314 } |
315 | 315 |
316 class InitialState extends InteractionState { | 316 class InitialState extends InteractionState { |
317 final InteractionContext context; | 317 final InteractionContext context; |
318 bool requestCodeCompletion = false; | 318 bool requestCodeCompletion = false; |
319 | 319 |
320 InitialState(this.context); | 320 InitialState(this.context); |
321 | 321 |
322 void set state(InteractionState state) { | 322 void set state(InteractionState state) { |
323 InteractionState previous = context.state; | 323 InteractionState previous = context.state; |
324 if (previous != state) { | 324 if (previous != state) { |
325 context.state = state; | 325 context.state = state; |
326 state.onStateChanged(previous); | 326 state.onStateChanged(previous); |
327 } | 327 } |
328 } | 328 } |
329 | 329 |
330 void onInput(Event event) { | 330 void onInput(Event event) { |
331 state = new PendingInputState(context); | 331 state = new PendingInputState(context); |
332 } | 332 } |
333 | 333 |
334 void onKeyUp(KeyboardEvent event) { | 334 void onKeyUp(KeyboardEvent event) { |
335 if (computeHasModifier(event)) { | 335 if (computeHasModifier(event)) { |
336 print('onKeyUp (modified)'); | |
337 onModifiedKeyUp(event); | 336 onModifiedKeyUp(event); |
338 } else { | 337 } else { |
339 print('onKeyUp (unmodified)'); | |
340 onUnmodifiedKeyUp(event); | 338 onUnmodifiedKeyUp(event); |
341 } | 339 } |
342 } | 340 } |
343 | 341 |
344 void onModifiedKeyUp(KeyboardEvent event) { | 342 void onModifiedKeyUp(KeyboardEvent event) { |
345 } | 343 } |
346 | 344 |
347 void onUnmodifiedKeyUp(KeyboardEvent event) { | 345 void onUnmodifiedKeyUp(KeyboardEvent event) { |
348 switch (event.keyCode) { | 346 switch (event.keyCode) { |
349 case KeyCode.ENTER: { | 347 case KeyCode.ENTER: { |
(...skipping 26 matching lines...) Expand all Loading... | |
376 | 374 |
377 // This is a hack to get Safari (iOS) to send mutation events on | 375 // This is a hack to get Safari (iOS) to send mutation events on |
378 // contenteditable. | 376 // contenteditable. |
379 // TODO(ahe): Move to onInput? | 377 // TODO(ahe): Move to onInput? |
380 var newDiv = new DivElement(); | 378 var newDiv = new DivElement(); |
381 hackDiv.replaceWith(newDiv); | 379 hackDiv.replaceWith(newDiv); |
382 hackDiv = newDiv; | 380 hackDiv = newDiv; |
383 } | 381 } |
384 | 382 |
385 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { | 383 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { |
386 print('onMutation'); | |
387 | |
388 removeCodeCompletion(); | 384 removeCodeCompletion(); |
389 | 385 |
390 Selection selection = window.getSelection(); | 386 Selection selection = window.getSelection(); |
391 TrySelection trySelection = new TrySelection(mainEditorPane, selection); | 387 TrySelection trySelection = new TrySelection(mainEditorPane, selection); |
392 | 388 |
393 Set<Node> normalizedNodes = new Set<Node>(); | 389 Set<Node> normalizedNodes = new Set<Node>(); |
394 for (MutationRecord record in mutations) { | 390 for (MutationRecord record in mutations) { |
395 normalizeMutationRecord(record, trySelection, normalizedNodes); | 391 normalizeMutationRecord(record, trySelection, normalizedNodes); |
396 } | 392 } |
397 | 393 |
(...skipping 21 matching lines...) Expand all Loading... | |
419 for (String line in splitLines(currentText)) { | 415 for (String line in splitLines(currentText)) { |
420 List<Node> lineNodes = <Node>[]; | 416 List<Node> lineNodes = <Node>[]; |
421 state = tokenizeAndHighlight( | 417 state = tokenizeAndHighlight( |
422 line, state, offset, trySelection, lineNodes); | 418 line, state, offset, trySelection, lineNodes); |
423 offset += line.length; | 419 offset += line.length; |
424 nodes.add(makeLine(lineNodes, state)); | 420 nodes.add(makeLine(lineNodes, state)); |
425 } | 421 } |
426 | 422 |
427 node.parent.insertAllBefore(nodes, node); | 423 node.parent.insertAllBefore(nodes, node); |
428 node.remove(); | 424 node.remove(); |
429 trySelection.adjust(selection); | 425 if (mainEditorPane.contains(trySelection.anchorNode)) { |
ahe
2014/06/27 11:23:28
This sometimes happens on Firefox. It is related t
Johnni Winther
2014/07/02 08:40:54
Put this in a comment.
ahe
2014/07/04 13:52:00
Done.
| |
426 trySelection.adjust(selection); | |
427 } | |
430 | 428 |
431 // TODO(ahe): We know almost exactly what has changed. It could be | 429 // TODO(ahe): We know almost exactly what has changed. It could be |
432 // more efficient to only communicate what changed. | 430 // more efficient to only communicate what changed. |
433 context.currentCompilationUnit.content = getText(mainEditorPane); | 431 context.currentCompilationUnit.content = getText(mainEditorPane); |
434 | 432 |
435 // Discard highlighting mutations. | 433 // Discard highlighting mutations. |
436 observer.takeRecords(); | 434 observer.takeRecords(); |
437 return; | 435 return; |
438 } | 436 } |
439 } | 437 } |
(...skipping 703 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1143 Text newNode = new Text('$buffer'); | 1141 Text newNode = new Text('$buffer'); |
1144 node.replaceWith(newNode); | 1142 node.replaceWith(newNode); |
1145 if (selectionOffset != -1) { | 1143 if (selectionOffset != -1) { |
1146 selection.anchorNode = newNode; | 1144 selection.anchorNode = newNode; |
1147 selection.anchorOffset = selectionOffset; | 1145 selection.anchorOffset = selectionOffset; |
1148 } | 1146 } |
1149 } | 1147 } |
1150 if (!record.removedNodes.isEmpty) { | 1148 if (!record.removedNodes.isEmpty) { |
1151 normalizedNodes.add(findLine(record.target)); | 1149 normalizedNodes.add(findLine(record.target)); |
1152 } | 1150 } |
1153 if (record.type == "characterData") { | 1151 if (record.type == "characterData" && record.target.parent != null) { |
ahe
2014/06/27 11:23:28
Firefox sends two records when the last character
Johnni Winther
2014/07/02 08:40:54
Put this in a comment.
ahe
2014/07/04 13:52:00
Done.
| |
1154 normalizedNodes.add(findLine(record.target)); | 1152 normalizedNodes.add(findLine(record.target)); |
1155 } | 1153 } |
1156 } | 1154 } |
1157 | 1155 |
1158 // Finds the line of [node] (a parent node with CSS class 'lineNumber'). | 1156 // Finds the line of [node] (a parent node with CSS class 'lineNumber'). |
1159 // If no such parent exists, return mainEditorPane if it is a parent. | 1157 // If no such parent exists, return mainEditorPane if it is a parent. |
1160 // Otherwise return [node]. | 1158 // Otherwise return [node]. |
1161 Node findLine(Node node) { | 1159 Node findLine(Node node) { |
1162 for (Node n = node; n != null; n = n.parent) { | 1160 for (Node n = node; n != null; n = n.parent) { |
1163 if (n is Element && n.classes.contains('lineNumber')) return n; | 1161 if (n is Element && n.classes.contains('lineNumber')) return n; |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1196 bool isCompilerStageMarker(String message) { | 1194 bool isCompilerStageMarker(String message) { |
1197 return | 1195 return |
1198 message.startsWith('Package root is ') || | 1196 message.startsWith('Package root is ') || |
1199 message.startsWith('Compiling ') || | 1197 message.startsWith('Compiling ') || |
1200 message == "Resolving..." || | 1198 message == "Resolving..." || |
1201 message.startsWith('Resolved ') || | 1199 message.startsWith('Resolved ') || |
1202 message == "Inferring types..." || | 1200 message == "Inferring types..." || |
1203 message == "Compiling..." || | 1201 message == "Compiling..." || |
1204 message.startsWith('Compiled '); | 1202 message.startsWith('Compiled '); |
1205 } | 1203 } |
1204 | |
1205 void workAroundFirefoxBug() { | |
1206 Selection selection = window.getSelection(); | |
1207 if (!isCollapsed(selection)) return; | |
1208 Node node = selection.anchorNode; | |
1209 int offset = selection.anchorOffset; | |
1210 if (selection.anchorNode is Element && selection.anchorOffset != 0) { | |
ahe
2014/06/27 11:23:28
In some cases, Firefox reports the wrong anchorOff
Johnni Winther
2014/07/02 08:40:54
Again, put this in a comment.
ahe
2014/07/04 13:52:00
Done.
| |
1211 selection | |
1212 ..modify('move', 'backward', 'character') | |
1213 ..modify('move', 'forward', 'character'); | |
1214 print('Worked around Firefox selection bug $node@$offset -> ' | |
1215 '${selection.anchorNode}@${selection.anchorOffset}.'); | |
1216 } | |
1217 } | |
OLD | NEW |