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
 
226
 
227
     if (_delta != _root.toDelta()) {
227
     if (_delta != _root.toDelta()) {
228
       throw new StateError('Compose produced inconsistent results. '
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
     _controller.add(new NotusChange(before, change, source));
231
     _controller.add(new NotusChange(before, change, source));
232
   }
232
   }

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

80
   @override
80
   @override
81
   Delta apply(Delta document, int index, int length) {
81
   Delta apply(Delta document, int index, int length) {
82
     DeltaIterator iter = new DeltaIterator(document);
82
     DeltaIterator iter = new DeltaIterator(document);
83
+
83
     // First, check if line-break deleted after an embed.
84
     // First, check if line-break deleted after an embed.
84
     Operation op = iter.skip(index);
85
     Operation op = iter.skip(index);
85
     int indexDelta = 0;
86
     int indexDelta = 0;
86
     int lengthDelta = 0;
87
     int lengthDelta = 0;
87
     int remaining = length;
88
     int remaining = length;
88
     bool foundEmbed = false;
89
     bool foundEmbed = false;
90
+    bool hasLineBreakBefore = false;
89
     if (op != null && op.data.endsWith(kZeroWidthSpace)) {
91
     if (op != null && op.data.endsWith(kZeroWidthSpace)) {
90
       foundEmbed = true;
92
       foundEmbed = true;
91
       Operation candidate = iter.next(1);
93
       Operation candidate = iter.next(1);
102
           lengthDelta += 1;
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
     // Second, check if line-break deleted before an embed.
112
     // Second, check if line-break deleted before an embed.
108
     op = iter.skip(remaining);
113
     op = iter.skip(remaining);
109
     if (op != null && op.data.endsWith('\n')) {
114
     if (op != null && op.data.endsWith('\n')) {
110
       final candidate = iter.next(1);
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
         foundEmbed = true;
119
         foundEmbed = true;
113
         lengthDelta -= 1;
120
         lengthDelta -= 1;
114
       }
121
       }
115
     }
122
     }
123
+
116
     if (foundEmbed) {
124
     if (foundEmbed) {
117
       return new Delta()
125
       return new Delta()
118
         ..retain(index + indexDelta)
126
         ..retain(index + indexDelta)

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

110
         ..delete(1);
110
         ..delete(1);
111
       expect(actual, expected);
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
   zefyr:
19
   zefyr:
20
     path: ../
20
     path: ../
21
 
21
 
22
+dependency_overrides:
23
+  notus:
24
+    path: ../../notus
25
+
22
 dev_dependencies:
26
 dev_dependencies:
23
   flutter_test:
27
   flutter_test:
24
     sdk: flutter
28
     sdk: flutter

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

26
     } else if (userOp.isDelete && actualOp.isRetain) {
26
     } else if (userOp.isDelete && actualOp.isRetain) {
27
       diff += userOp.length;
27
       diff += userOp.length;
28
     } else if (userOp.isRetain && actualOp.isInsert) {
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
       diff += actualOp.length;
34
       diff += actualOp.length;
30
     } else {
35
     } else {
31
       // TODO: this likely needs to cover more edge cases.
36
       // TODO: this likely needs to cover more edge cases.