mirror of
https://github.com/dart-lang/sdk
synced 2024-07-21 10:25:52 +00:00
Better handling of large files.
R=kasperl@google.com Review URL: https://codereview.chromium.org//265063002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@35801 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
parent
5e65be57fa
commit
d8d2d2978a
|
@ -14,6 +14,7 @@ TODO(ahe): Reduce the number of fonts used based on actual usage.
|
|||
|
||||
See: http://www.google.com/fonts#UsePlace:use/Collection:Open+Sans:400,600,700,800,300
|
||||
-->
|
||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
|
||||
<link href='http://fonts.googleapis.com/css?family=Open+Sans:400,600,700,800,300' rel='stylesheet' type='text/css'>
|
||||
<link rel="stylesheet" type="text/css" href="dartlang-style.css">
|
||||
<link rel="alternate stylesheet" type="text/css" href="line_numbers.css" title="line_numbers">
|
||||
|
@ -34,7 +35,7 @@ a:hover.diagnostic span {
|
|||
position: absolute;
|
||||
/* left: 1em; */
|
||||
/* top: 2em; */
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.offline {
|
||||
|
@ -211,7 +212,7 @@ a:hover.diagnostic span {
|
|||
<a class="brand" href="//www.dartlang.org/" title="Dart Homepage" target="_blank">
|
||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAMAAACeyVWkAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAJxQTFRFAAAAAIvMdsvDAIvMdsvDAIvMdsvDLaTJAIvMOqnHdsvDAIvMdsvDAIvMKaLJdsvDAIvMAIvMdsvDAIvMdsvDdsvDAIvMAIvMAZnFdsvDAILHAIPHAITIAIXJAIfKAIjKAIrLAIrMAIvMAJXHAJjFC5i/I6HENr2yOb6zPr+0TsK4UsO5WbnEWcW8Xsa9Yse+Zsi/asjAc8rCdsvDdt4SRQAAABp0Uk5TABAQICAwMFBgYGBwcICAgI+vr7+/z9/v7+97IXGnAAAAqUlEQVQYV13QxxaCQBBE0VZkjBgAGVEBaVEUM/P//yaTGg5vV3dZANTCZ9BvFAoR93kVC9FnthW6uIPTJ7UkdHaXvS2LXKNBURInyDXPsShbzjU7XCpxhooDVGo5QcQAJmjUco64AY/UcIrowYCTaj5KBZeTaj5JBTc6l11OlQKMf497y1ahefFb3TQfcqtM/fipJF/X9gnDon6/ah/aDDfNOgosNA2b8QdGciZlh/U93AAAAABJRU5ErkJggg==" alt="Dart">
|
||||
</a>
|
||||
<ul class="nav pull-right"><li><a href="#" id="settings"><i class="icon-cog"></i></a></li></ul>
|
||||
<ul class="nav pull-right"><li><a href="https://code.google.com/p/dart/issues/entry?template=Try+Dart+Bug" target="_blank"><i class="fa fa-bug"></i></a></li><li><a href="#" id="settings"><i class="icon-cog"></i></a></li></ul>
|
||||
|
||||
<ul class="nav hidden-phone">
|
||||
<li class="active"><a>Try Dart!</a></li>
|
||||
|
|
|
@ -5,9 +5,6 @@
|
|||
|
||||
.mainEditorPane {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
div {
|
||||
counter-reset: dart-line-number;
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class TrySelection {
|
|||
|
||||
Text textNode = new Text(text.substring(start, end));
|
||||
|
||||
if (start <= globalOffset && globalOffset < end) {
|
||||
if (start <= globalOffset && globalOffset <= end) {
|
||||
anchorNode = textNode;
|
||||
anchorOffset = globalOffset - start;
|
||||
}
|
||||
|
|
144
tests/try/cursor_position_test.dart
Normal file
144
tests/try/cursor_position_test.dart
Normal file
|
@ -0,0 +1,144 @@
|
|||
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// SharedOptions=--package-root=sdk/lib/_internal/
|
||||
|
||||
// Test that cursor positions are correctly updated after adding new content.
|
||||
|
||||
library trydart.cursor_position_test;
|
||||
|
||||
import 'dart:html';
|
||||
import 'dart:async';
|
||||
|
||||
import '../../site/try/src/interaction_manager.dart' show
|
||||
InteractionManager;
|
||||
|
||||
import '../../site/try/src/ui.dart' show
|
||||
hackDiv,
|
||||
mainEditorPane,
|
||||
observer;
|
||||
|
||||
import '../../site/try/src/user_option.dart' show
|
||||
UserOption;
|
||||
|
||||
import '../../pkg/expect/lib/expect.dart';
|
||||
|
||||
import '../../pkg/async_helper/lib/async_helper.dart';
|
||||
|
||||
void main() {
|
||||
InteractionManager interaction = mockTryDartInteraction();
|
||||
|
||||
List<TestCase> tests = <TestCase>[
|
||||
|
||||
new TestCase('Test adding two lines programmatically.', () {
|
||||
clearEditorPaneWithoutNotifications();
|
||||
mainEditorPane.appendText('\n\n');
|
||||
Text text = mainEditorPane.firstChild;
|
||||
window.getSelection().collapse(text, 1);
|
||||
checkSelectionIsCollapsed(text, 1);
|
||||
}, checkAtBeginningOfSecondLine),
|
||||
|
||||
new TestCase('Test adding a new line with mock key event.', () {
|
||||
clearEditorPaneWithoutNotifications();
|
||||
checkSelectionIsCollapsed(mainEditorPane, 0);
|
||||
simulateEnterKeyDown(interaction);
|
||||
}, checkAtBeginningOfSecondLine),
|
||||
|
||||
];
|
||||
|
||||
runTests(tests.iterator, completerForAsyncTest());
|
||||
}
|
||||
|
||||
void simulateEnterKeyDown(Interaction interaction) {
|
||||
interaction.onKeyUp(
|
||||
new MockKeyboardEvent('keydown', keyCode: KeyCode.ENTER));
|
||||
}
|
||||
|
||||
void clearEditorPaneWithoutNotifications() {
|
||||
mainEditorPane.nodes.clear();
|
||||
observer.takeRecords();
|
||||
}
|
||||
|
||||
void checkSelectionIsCollapsed(Node node, int offset) {
|
||||
var selection = window.getSelection();
|
||||
Expect.isTrue(selection.isCollapsed, 'selection.isCollapsed');
|
||||
Expect.equals(node, selection.anchorNode, 'selection.anchorNode');
|
||||
Expect.equals(offset, selection.anchorOffset, 'selection.anchorOffset');
|
||||
}
|
||||
|
||||
void checkLineCount(int expectedLineCount) {
|
||||
Expect.equals(
|
||||
expectedLineCount, mainEditorPane.nodes.length,
|
||||
'mainEditorPane.nodes.length');
|
||||
}
|
||||
|
||||
void checkAtBeginningOfSecondLine() {
|
||||
checkLineCount(2);
|
||||
checkSelectionIsCollapsed(mainEditorPane.nodes[1].firstChild, 0);
|
||||
}
|
||||
|
||||
runTests(Iterator<TestCase> iterator, Completer completer) {
|
||||
if (iterator.moveNext()) {
|
||||
TestCase test = iterator.current;
|
||||
new Future(() {
|
||||
print('${test.description}\nSetup.');
|
||||
test.setup();
|
||||
new Future(() {
|
||||
test.validate();
|
||||
print('${test.description}\nDone.');
|
||||
runTests(iterator, completer);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
completer.complete(null);
|
||||
}
|
||||
}
|
||||
|
||||
Completer completerForAsyncTest() {
|
||||
Completer completer = new Completer();
|
||||
asyncTest(() => completer.future.then((_) {
|
||||
// Clear the DOM to work around a bug in test.dart.
|
||||
document.body.nodes.clear();
|
||||
}));
|
||||
return completer;
|
||||
}
|
||||
|
||||
InteractionManager mockTryDartInteraction() {
|
||||
UserOption.storage = {};
|
||||
|
||||
InteractionManager interaction = new InteractionManager();
|
||||
|
||||
hackDiv = new DivElement();
|
||||
mainEditorPane = new DivElement()
|
||||
..style.whiteSpace = 'pre'
|
||||
..contentEditable = 'true';
|
||||
|
||||
observer = new MutationObserver(interaction.onMutation);
|
||||
observer.observe(
|
||||
mainEditorPane, childList: true, characterData: true, subtree: true);
|
||||
|
||||
document.body.nodes.addAll([mainEditorPane, hackDiv]);
|
||||
|
||||
return interaction;
|
||||
}
|
||||
|
||||
class MockKeyboardEvent extends KeyEvent {
|
||||
final int keyCode;
|
||||
|
||||
MockKeyboardEvent(String type, {int keyCode})
|
||||
: this.keyCode = keyCode,
|
||||
super.wrap(new KeyEvent(type, keyCode: keyCode));
|
||||
|
||||
bool getModifierState(String keyArgument) => false;
|
||||
}
|
||||
|
||||
typedef void VoidFunction();
|
||||
|
||||
class TestCase {
|
||||
final String description;
|
||||
final VoidFunction setup;
|
||||
final VoidFunction validate;
|
||||
|
||||
TestCase(this.description, this.setup, this.validate);
|
||||
}
|
Loading…
Reference in a new issue