Browse Source

Improved handling of user input around embeds

Anatoly Pulyaevskiy 6 years ago
parent
commit
19da3daec4

+ 1
- 1
packages/notus/lib/src/document.dart View File

@@ -226,7 +226,7 @@ class NotusDocument {
226 226
 
227 227
     if (_delta != _root.toDelta()) {
228 228
       throw new StateError('Compose produced inconsistent results. '
229
-          'This is likely due to a bug in the library.');
229
+          'This is likely due to a bug in the library. Tried to compose change $change from $source.');
230 230
     }
231 231
     _controller.add(new NotusChange(before, change, source));
232 232
   }

+ 9
- 1
packages/notus/lib/src/heuristics/delete_rules.dart View File

@@ -80,12 +80,14 @@ class EnsureEmbedLineRule extends DeleteRule {
80 80
   @override
81 81
   Delta apply(Delta document, int index, int length) {
82 82
     DeltaIterator iter = new DeltaIterator(document);
83
+
83 84
     // First, check if line-break deleted after an embed.
84 85
     Operation op = iter.skip(index);
85 86
     int indexDelta = 0;
86 87
     int lengthDelta = 0;
87 88
     int remaining = length;
88 89
     bool foundEmbed = false;
90
+    bool hasLineBreakBefore = false;
89 91
     if (op != null && op.data.endsWith(kZeroWidthSpace)) {
90 92
       foundEmbed = true;
91 93
       Operation candidate = iter.next(1);
@@ -102,17 +104,23 @@ class EnsureEmbedLineRule extends DeleteRule {
102 104
           lengthDelta += 1;
103 105
         }
104 106
       }
107
+    } else {
108
+      // If op is `null` it's a beginning of the doc, e.g. implicit line break.
109
+      hasLineBreakBefore = op == null || op.data.endsWith('\n');
105 110
     }
106 111
 
107 112
     // Second, check if line-break deleted before an embed.
108 113
     op = iter.skip(remaining);
109 114
     if (op != null && op.data.endsWith('\n')) {
110 115
       final candidate = iter.next(1);
111
-      if (candidate.data == kZeroWidthSpace) {
116
+      // If there is a line-break before deleted range we allow the operation
117
+      // since it results in a correctly formatted line with single embed in it.
118
+      if (candidate.data == kZeroWidthSpace && !hasLineBreakBefore) {
112 119
         foundEmbed = true;
113 120
         lengthDelta -= 1;
114 121
       }
115 122
     }
123
+
116 124
     if (foundEmbed) {
117 125
       return new Delta()
118 126
         ..retain(index + indexDelta)

+ 14
- 0
packages/notus/test/heuristics/delete_rules_test.dart View File

@@ -110,5 +110,19 @@ void main() {
110 110
         ..delete(1);
111 111
       expect(actual, expected);
112 112
     });
113
+
114
+    test('allows deleting empty line(s) before embed', () {
115
+      final hr = NotusAttribute.embed.horizontalRule;
116
+      final doc = new Delta()
117
+        ..insert('Document\n')
118
+        ..insert('\n')
119
+        ..insert('\n')
120
+        ..insert(kZeroWidthSpace, hr.toJson())
121
+        ..insert('\n')
122
+        ..insert('Text')
123
+        ..insert('\n');
124
+      final actual = rule.apply(doc, 11, 1);
125
+      expect(actual, isNull);
126
+    });
113 127
   });
114 128
 }

+ 4
- 0
packages/zefyr/example/pubspec.yaml View File

@@ -19,6 +19,10 @@ dependencies:
19 19
   zefyr:
20 20
     path: ../
21 21
 
22
+dependency_overrides:
23
+  notus:
24
+    path: ../../notus
25
+
22 26
 dev_dependencies:
23 27
   flutter_test:
24 28
     sdk: flutter

+ 5
- 0
packages/zefyr/lib/util.dart View File

@@ -26,6 +26,11 @@ int getPositionDelta(Delta user, Delta actual) {
26 26
     } else if (userOp.isDelete && actualOp.isRetain) {
27 27
       diff += userOp.length;
28 28
     } else if (userOp.isRetain && actualOp.isInsert) {
29
+      if (actualOp.data.startsWith('\n') ) {
30
+        // At this point user input reached its end (retain). If a heuristic
31
+        // rule inserts a new line we should keep cursor on it's original position.
32
+        continue;
33
+      }
29 34
       diff += actualOp.length;
30 35
     } else {
31 36
       // TODO: this likely needs to cover more edge cases.