| Index: dart/site/try/src/interaction_manager.dart
|
| diff --git a/dart/site/try/src/interaction_manager.dart b/dart/site/try/src/interaction_manager.dart
|
| index 5b309a96cbcbe2fff1d870ac2d033120d9539b94..4d4dd24d06b7b0a7e121b040d4895eae4d6983b6 100644
|
| --- a/dart/site/try/src/interaction_manager.dart
|
| +++ b/dart/site/try/src/interaction_manager.dart
|
| @@ -92,6 +92,8 @@ abstract class InteractionManager {
|
|
|
| void onInput(Event event);
|
|
|
| + // TODO(ahe): Rename to onKeyDown (as it is called in response to keydown
|
| + // event).
|
| void onKeyUp(KeyboardEvent event);
|
|
|
| void onMutation(List<MutationRecord> mutations, MutationObserver observer);
|
| @@ -228,13 +230,28 @@ class InitialState extends InteractionState {
|
| void onUnmodifiedKeyUp(KeyboardEvent event) {
|
| switch (event.keyCode) {
|
| case KeyCode.ENTER: {
|
| - event.preventDefault();
|
| Selection selection = window.getSelection();
|
| - if (isCollapsed(selection) && selection.anchorNode is Text) {
|
| - Text text = selection.anchorNode;
|
| - int offset = selection.anchorOffset;
|
| - text.insertData(offset, '\n');
|
| - selection.collapse(text, offset + 1);
|
| + if (isCollapsed(selection)) {
|
| + event.preventDefault();
|
| + Node node = selection.anchorNode;
|
| + if (node is Text) {
|
| + Text text = node;
|
| + int offset = selection.anchorOffset;
|
| + // If at end-of-file, insert an extra newline. The the extra
|
| + // newline ensures that the next line isn't empty. At least Chrome
|
| + // behaves as if "\n" is just a single line. "\nc" (where c is any
|
| + // character) is two lines, according to Chrome.
|
| + String newline = isAtEndOfFile(text, offset) ? '\n\n' : '\n';
|
| + text.insertData(offset, newline);
|
| + selection.collapse(text, offset + 1);
|
| + } else if (node is Element) {
|
| + node.appendText('\n\n');
|
| + selection.collapse(node.firstChild, 1);
|
| + } else {
|
| + window.console
|
| + ..error('Unexpected node')
|
| + ..dir(node);
|
| + }
|
| }
|
| break;
|
| }
|
| @@ -262,8 +279,46 @@ class InitialState extends InteractionState {
|
| Selection selection = window.getSelection();
|
| TrySelection trySelection = new TrySelection(mainEditorPane, selection);
|
|
|
| + Set<Node> normalizedNodes = new Set<Node>();
|
| for (MutationRecord record in mutations) {
|
| - normalizeMutationRecord(record, trySelection);
|
| + normalizeMutationRecord(record, trySelection, normalizedNodes);
|
| + }
|
| +
|
| + if (normalizedNodes.length == 1) {
|
| + Node node = normalizedNodes.single;
|
| + if (node is Element && node.classes.contains('lineNumber')) {
|
| + print('Single line change: ${node.outerHtml}');
|
| +
|
| + String currentText = node.text;
|
| +
|
| + trySelection = new TrySelection(node, selection);
|
| + trySelection.updateText(currentText);
|
| +
|
| + editor.isMalformedInput = false;
|
| + int offset = 0;
|
| + List<Node> nodes = <Node>[];
|
| +
|
| + String state = '';
|
| + Element previousLine = node.previousElementSibling;
|
| + if (previousLine != null) {
|
| + state = previousLine.getAttribute('dart-state');
|
| + }
|
| + for (String line in splitLines(currentText)) {
|
| + List<Node> lineNodes = <Node>[];
|
| + state = tokenizeAndHighlight(
|
| + line, state, offset, trySelection, lineNodes);
|
| + offset += line.length;
|
| + nodes.add(makeLine(lineNodes, state));
|
| + }
|
| +
|
| + node.parent.insertAllBefore(nodes, node);
|
| + node.remove();
|
| + trySelection.adjust(selection);
|
| +
|
| + // Discard highlighting mutations.
|
| + observer.takeRecords();
|
| + return;
|
| + }
|
| }
|
|
|
| String currentText = mainEditorPane.text;
|
| @@ -278,14 +333,12 @@ class InitialState extends InteractionState {
|
| List<Node> nodes = <Node>[];
|
|
|
| String state = '';
|
| - for (String line in currentText.split(new RegExp('^', multiLine: true))) {
|
| + for (String line in splitLines(currentText)) {
|
| List<Node> lineNodes = <Node>[];
|
| state =
|
| tokenizeAndHighlight(line, state, offset, trySelection, lineNodes);
|
| offset += line.length;
|
| - nodes.add(new SpanElement()
|
| - ..nodes.addAll(lineNodes)
|
| - ..classes.add('lineNumber'));
|
| + nodes.add(makeLine(lineNodes, state));
|
| }
|
|
|
| mainEditorPane
|
| @@ -785,17 +838,60 @@ bool isUnterminatedMultiLineToken(UnterminatedToken token) {
|
| token.start == 'r"""';
|
| }
|
|
|
| -void normalizeMutationRecord(MutationRecord record, TrySelection selection) {
|
| - if (record.addedNodes.isEmpty) return;
|
| +void normalizeMutationRecord(MutationRecord record,
|
| + TrySelection selection,
|
| + Set<Node> normalizedNodes) {
|
| for (Node node in record.addedNodes) {
|
| if (node.parent == null) continue;
|
| StringBuffer buffer = new StringBuffer();
|
| int selectionOffset = htmlToText(node, buffer, selection);
|
| Text newNode = new Text('$buffer');
|
| node.replaceWith(newNode);
|
| + normalizedNodes.add(findLine(newNode));
|
| if (selectionOffset != -1) {
|
| selection.anchorNode = newNode;
|
| selection.anchorOffset = selectionOffset;
|
| }
|
| }
|
| + if (!record.removedNodes.isEmpty) {
|
| + normalizedNodes.add(findLine(record.target));
|
| + }
|
| + if (record.type == "characterData") {
|
| + normalizedNodes.add(findLine(record.target));
|
| + }
|
| +}
|
| +
|
| +// Finds the line of [node] (a parent node with CSS class 'lineNumber').
|
| +// If no such parent exists, return mainEditorPane if it is a parent.
|
| +// Otherwise return [node].
|
| +Node findLine(Node node) {
|
| + for (Node n = node; n != null; n = n.parent) {
|
| + if (n is Element && n.classes.contains('lineNumber')) return n;
|
| + if (n == mainEditorPane) return n;
|
| + }
|
| + return node;
|
| +}
|
| +
|
| +Element makeLine(List<Node> lineNodes, String state) {
|
| + // Using a div element here (anything with display=block) generally messes up
|
| + // editing and navigation. We would like to use a block element here so
|
| + // error messages show as expected. But no such luck. Fortunately, there
|
| + // are strong indications that the current solution for displaying errors
|
| + // isn't good enough anyways.
|
| + return new SpanElement()
|
| + ..setAttribute('dart-state', state)
|
| + ..nodes.addAll(lineNodes)
|
| + ..classes.add('lineNumber');
|
| +}
|
| +
|
| +bool isAtEndOfFile(Text text, int offset) {
|
| + Node line = findLine(text);
|
| + return
|
| + line.nextNode == null &&
|
| + text.parent.nextNode == null &&
|
| + offset == text.length;
|
| +}
|
| +
|
| +List<String> splitLines(String text) {
|
| + return text.split(new RegExp('^', multiLine: true));
|
| }
|
|
|