Rich text editor for Flutter
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

310 lines
6.8 KiB

import 'dart:math' as math;
import 'package:flutter_quill/models/documents/attribute.dart';
import 'package:flutter_quill/models/documents/nodes/node.dart';
import 'package:quill_delta/quill_delta.dart';
import '../style.dart';
import 'block.dart';
import 'container.dart';
import 'embed.dart';
import 'leaf.dart';
class Line extends Container<Leaf> {
@override
Leaf get defaultChild => Text();
@override
int get length => super.length + 1;
bool get hasEmbed {
if (childCount != 1) {
return false;
}
return children.single is Embed;
}
Line get nextLine {
if (!isLast) {
return next is Block ? (next as Block).first : next;
}
if (parent is! Block) {
return null;
}
if (parent.isLast) {
return null;
}
return parent.next is Block ? (parent.next as Block).first : parent.next;
}
@override
Delta toDelta() {
final delta = children
.map((child) => child.toDelta())
.fold(Delta(), (a, b) => a.concat(b));
var attributes = style;
if (parent is Block) {
Block block = parent;
attributes = attributes.mergeAll(block.style);
}
delta.insert('\n', attributes.toJson());
return delta;
}
@override
String toPlainText() => super.toPlainText() + '\n';
@override
insert(int index, Object data, Style style) {
if (data is Embeddable) {
_insert(index, data, style);
return;
}
String text = data as String;
int lineBreak = text.indexOf('\n');
if (lineBreak < 0) {
_insert(index, text, style);
return;
}
String prefix = text.substring(0, lineBreak);
_insert(index, prefix, style);
if (prefix.isNotEmpty) {
index += prefix.length;
}
Line nextLine = _getNextLine(index);
clearStyle();
if (parent is Block) {
_unwrap();
}
_format(style);
// Continue with the remaining
String remain = text.substring(lineBreak + 1);
nextLine.insert(0, remain, style);
}
@override
retain(int index, int len, Style style) {
if (style == null) {
return;
}
int thisLen = this.length;
int local = math.min(thisLen - index, length);
assert(style.values.every((attr) => attr.scope == AttributeScope.BLOCK));
if (index + local == thisLen && local == 1) {
_format(style);
} else {
assert(index + local != thisLen);
super.retain(index, local, style);
}
int remain = len - local;
if (remain > 0) {
assert(nextLine != null);
nextLine.retain(0, remain, style);
}
}
@override
delete(int index, int len) {
int local = math.min(this.length - index, len);
bool deleted = index + local == this.length;
if (deleted) {
clearStyle();
if (local > 1) {
super.delete(index, local - 1);
}
} else {
super.delete(index, local);
}
int remain = len - local;
if (remain > 0) {
assert(nextLine != null);
nextLine.delete(0, remain);
}
if (deleted && isNotEmpty) {
assert(nextLine != null);
nextLine.moveChildToNewParent(this);
moveChildToNewParent(nextLine);
}
if (deleted) {
Node p = parent;
unlink();
p.adjust();
}
}
void _format(Style newStyle) {
if (newStyle == null || newStyle.isEmpty) {
return;
}
applyStyle(newStyle);
Attribute blockStyle = newStyle.getBlockExceptHeader();
if (blockStyle == null) {
return;
}
if (parent is Block) {
Attribute parentStyle = (parent as Block).style.getBlockExceptHeader();
if (blockStyle.value == null) {
_unwrap();
} else if (blockStyle != parentStyle) {
_unwrap();
Block block = Block();
block.applyAttribute(blockStyle);
_wrap(block);
block.adjust();
}
} else if (blockStyle.value != null) {
Block block = Block();
block.applyAttribute(blockStyle);
_wrap(block);
block.adjust();
}
}
_wrap(Block block) {
assert(parent != null && parent is! Block);
insertAfter(block);
unlink();
block.add(this);
}
_unwrap() {
if (parent is! Block) {
throw ArgumentError('Invalid parent');
}
Block block = parent;
assert(block.children.contains(this));
if (isFirst) {
unlink();
block.insertBefore(this);
} else if (isLast) {
unlink();
block.insertAfter(this);
} else {
Block before = block.clone();
block.insertBefore(before);
Line child = block.first;
while (child != this) {
child.unlink();
before.add(child);
child = block.first as Line;
}
unlink();
block.insertBefore(this);
}
block.adjust();
}
Line _getNextLine(int index) {
assert(index == 0 || (index > 0 && index < length));
Line line = clone() as Line;
insertAfter(line);
if (index == length - 1) {
return line;
}
ChildQuery query = queryChild(index, false);
while (!query.node.isLast) {
Leaf next = last;
next.unlink();
line.addFirst(next);
}
Leaf child = query.node;
Leaf cut = child.splitAt(query.offset);
cut?.unlink();
line.addFirst(cut);
return line;
}
_insert(int index, Object data, Style style) {
assert(index == 0 || (index > 0 && index < length));
if (data is String) {
assert(!data.contains('\n'));
if (data.isEmpty) {
return;
}
}
if (isNotEmpty) {
ChildQuery result = queryChild(index, true);
result.node.insert(result.offset, data, style);
return;
}
Leaf child = Leaf(data);
add(child);
child.format(style);
}
@override
Node newInstance() {
return Line();
}
Style collectStyle(int offset, int len) {
int local = math.min(this.length - offset, len);
Style res = Style();
var excluded = <Attribute>{};
void _handle(Style style) {
if (res.isEmpty) {
excluded.addAll(style.values);
} else {
for (Attribute attr in res.values) {
if (!style.containsKey(attr.key)) {
excluded.add(attr);
}
}
}
Style remain = style.removeAll(excluded);
res = res.removeAll(excluded);
res = res.mergeAll(remain);
}
ChildQuery data = queryChild(offset, true);
Leaf node = data.node;
if (node != null) {
res = res.mergeAll(node.style);
int pos = node.length - data.offset;
while (!node.isLast && pos < local) {
node = node.next as Leaf;
_handle(node.style);
pos += node.length;
}
}
res = res.mergeAll(style);
if (parent is Block) {
Block block = parent;
res = res.mergeAll(block.style);
}
int remain = len - local;
if (remain > 0) {
_handle(nextLine.collectStyle(0, remain));
}
return res;
}
}