diff --git a/lib/models/documents/nodes/block.dart b/lib/models/documents/nodes/block.dart index 5128f598..095f1183 100644 --- a/lib/models/documents/nodes/block.dart +++ b/lib/models/documents/nodes/block.dart @@ -3,7 +3,21 @@ import 'container.dart'; import 'line.dart'; import 'node.dart'; +/// Represents a group of adjacent [Line]s with the same block style. +/// +/// Block elements are: +/// - Blockquote +/// - Header +/// - Indent +/// - List +/// - Text Alignment +/// - Text Direction +/// - Code Block class Block extends Container { + /// Creates new unmounted [Block]. + @override + Node newInstance() => Block(); + @override Line get defaultChild => Line(); @@ -55,9 +69,4 @@ class Block extends Container { } return buffer.toString(); } - - @override - Node newInstance() { - return Block(); - } } diff --git a/lib/models/documents/nodes/container.dart b/lib/models/documents/nodes/container.dart index 28fc2475..dbdd12d1 100644 --- a/lib/models/documents/nodes/container.dart +++ b/lib/models/documents/nodes/container.dart @@ -1,48 +1,67 @@ import 'dart:collection'; import '../style.dart'; +import 'leaf.dart'; +import 'line.dart'; import 'node.dart'; -/* Container of multiple nodes */ +/// Container can accommodate other nodes. +/// +/// Delegates insert, retain and delete operations to children nodes. For each +/// operation container looks for a child at specified index position and +/// forwards operation to that child. +/// +/// Most of the operation handling logic is implemented by [Line] and [Text]. abstract class Container extends Node { final LinkedList _children = LinkedList(); + /// List of children. LinkedList get children => _children; + /// Returns total number of child nodes in this container. + /// + /// To get text length of this container see [length]. int get childCount => _children.length; + /// Returns the first child [Node]. Node get first => _children.first; + /// Returns the last child [Node]. Node get last => _children.last; + /// Returns `true` if this container has no child nodes. bool get isEmpty => _children.isEmpty; + /// Returns `true` if this container has at least 1 child. bool get isNotEmpty => _children.isNotEmpty; - /// abstract methods begin - + /// Returns an instance of default child for this container node. + /// + /// Always returns fresh instance. T get defaultChild; - /// abstract methods end - + /// Adds [node] to the end of this container children list. void add(T node) { assert(node?.parent == null); node?.parent = this; _children.add(node as Node); } + /// Adds [node] to the beginning of this container children list. void addFirst(T node) { assert(node?.parent == null); node?.parent = this; _children.addFirst(node as Node); } + /// Removes [node] from this container. void remove(T node) { assert(node?.parent == this); node?.parent = null; _children.remove(node as Node); } + /// Moves children of this node to [newParent]. void moveChildToNewParent(Container? newParent) { if (isEmpty) { return; @@ -55,9 +74,19 @@ abstract class Container extends Node { newParent.add(child); } + /// In case [newParent] already had children we need to make sure + /// combined list is optimized. if (last != null) last.adjust(); } + /// Queries the child [Node] at specified character [offset] in this container. + /// + /// The result may contain the found node or `null` if no node is found + /// at specified offset. + /// + /// [ChildQuery.offset] is set to relative offset within returned child node + /// which points at the same character position in the document as the + /// original [offset]. ChildQuery queryChild(int offset, bool inclusive) { if (offset < 0 || offset > length) { return ChildQuery(null, 0); @@ -76,6 +105,9 @@ abstract class Container extends Node { @override String toPlainText() => children.map((child) => child.toPlainText()).join(); + /// Content length of this node's children. + /// + /// To get number of children in this node use [childCount]. @override int get length => _children.fold(0, (cur, node) => cur + node.length); @@ -114,11 +146,15 @@ abstract class Container extends Node { String toString() => _children.join('\n'); } -/// Query of a child in a Container +/// Result of a child query in a [Container]. class ChildQuery { ChildQuery(this.node, this.offset); - final Node? node; // null if not found + /// The child node if found, otherwise `null`. + final Node? node; + /// Starting offset within the child [node] which points at the same + /// character in the document as the original offset passed to + /// [Container.queryChild] method. final int offset; } diff --git a/lib/models/documents/nodes/embed.dart b/lib/models/documents/nodes/embed.dart index 81ca6005..d6fe628a 100644 --- a/lib/models/documents/nodes/embed.dart +++ b/lib/models/documents/nodes/embed.dart @@ -1,7 +1,15 @@ +/// An object which can be embedded into a Quill document. +/// +/// See also: +/// +/// * [BlockEmbed] which represents a block embed. class Embeddable { Embeddable(this.type, this.data); + /// The type of this object. final String type; + + /// The data payload of this object. final dynamic data; Map toJson() { @@ -17,10 +25,16 @@ class Embeddable { } } +/// An object which occupies an entire line in a document and cannot co-exist +/// inline with regular text. +/// +/// There are two built-in embed types supported by Quill documents, however +/// the document model itself does not make any assumptions about the types +/// of embedded objects and allows users to define their own types. class BlockEmbed extends Embeddable { BlockEmbed(String type, String data) : super(type, data); - static final BlockEmbed horizontalRule = BlockEmbed('divider', 'hr'); + static BlockEmbed horizontalRule = BlockEmbed('divider', 'hr'); static BlockEmbed image(String imageUrl) => BlockEmbed('image', imageUrl); } diff --git a/lib/models/documents/nodes/leaf.dart b/lib/models/documents/nodes/leaf.dart index 24431fc9..bd9292f5 100644 --- a/lib/models/documents/nodes/leaf.dart +++ b/lib/models/documents/nodes/leaf.dart @@ -6,8 +6,9 @@ import 'embed.dart'; import 'line.dart'; import 'node.dart'; -/* A leaf node in document tree */ +/// A leaf in Quill document tree. abstract class Leaf extends Node { + /// Creates a new [Leaf] with specified [data]. factory Leaf(Object data) { if (data is Embeddable) { return Embed(data); @@ -19,9 +20,10 @@ abstract class Leaf extends Node { Leaf.val(Object val) : _value = val; - Object _value; - + /// Contents of this node, either a String if this is a [Text] or an + /// [Embed] if this is an [BlockEmbed]. Object get value => _value; + Object _value; @override void applyStyle(Style value) { @@ -99,14 +101,21 @@ abstract class Leaf extends Node { } } + /// Adjust this text node by merging it with adjacent nodes if they share + /// the same style. @override void adjust() { if (this is Embed) { + // Embed nodes cannot be merged with text nor other embeds (in fact, + // there could be no two adjacent embeds on the same line since an + // embed occupies an entire line). return; } + // This is a text node and it can only be merged with other text nodes. var node = this as Text; - // merging it with previous node if style is the same + + // Merging it with previous node if style is the same. final prev = node.previous; if (!node.isFirst && prev is Text && prev.style == node.style) { prev._value = prev.value + node.value; @@ -114,7 +123,7 @@ abstract class Leaf extends Node { node = prev; } - // merging it with next node if style is the same + // Merging it with next node if style is the same. final next = node.next; if (!node.isLast && next is Text && next.style == node.style) { node._value = node.value + next.value; @@ -122,13 +131,17 @@ abstract class Leaf extends Node { } } - Leaf? cutAt(int index) { - assert(index >= 0 && index <= length); - final cut = splitAt(index); - cut?.unlink(); - return cut; - } - + /// Splits this leaf node at [index] and returns new node. + /// + /// If this is the last node in its list and [index] equals this node's + /// length then this method returns `null` as there is nothing left to split. + /// If there is another leaf node after this one and [index] equals this + /// node's length then the next leaf node is returned. + /// + /// If [index] equals to `0` then this node itself is returned unchanged. + /// + /// In case a new node is actually split from this one, it inherits this + /// node's style. Leaf? splitAt(int index) { assert(index >= 0 && index <= length); if (index == 0) { @@ -146,14 +159,33 @@ abstract class Leaf extends Node { return split; } + /// Cuts a leaf from [index] to the end of this node and returns new node + /// in detached state (e.g. [mounted] returns `false`). + /// + /// Splitting logic is identical to one described in [splitAt], meaning this + /// method may return `null`. + Leaf? cutAt(int index) { + assert(index >= 0 && index <= length); + final cut = splitAt(index); + cut?.unlink(); + return cut; + } + + /// Formats this node and optimizes it with adjacent leaf nodes if needed. void format(Style? style) { if (style != null && style.isNotEmpty) { applyStyle(style); } - adjust(); } + /// Isolates a new leaf starting at [index] with specified [length]. + /// + /// Splitting logic is identical to one described in [splitAt], with one + /// exception that it is required for [index] to always be less than this + /// node's length. As a result this method always returns a [LeafNode] + /// instance. Returned node may still be the same as this node + /// if provided [index] is `0`. Leaf _isolate(int index, int length) { assert( index >= 0 && index < this.length && (index + length <= this.length)); @@ -162,39 +194,59 @@ abstract class Leaf extends Node { } } +/// A span of formatted text within a line in a Quill document. +/// +/// Text is a leaf node of a document tree. +/// +/// Parent of a text node is always a [Line], and as a consequence text +/// node's [value] cannot contain any line-break characters. +/// +/// See also: +/// +/// * [Embed], a leaf node representing an embeddable object. +/// * [Line], a node representing a line of text. class Text extends Leaf { Text([String text = '']) : assert(!text.contains('\n')), super.val(text); @override - String get value => _value as String; + Node newInstance() => Text(); @override - String toPlainText() { - return value; - } + String get value => _value as String; @override - Node newInstance() { - return Text(); - } + String toPlainText() => value; } -/// An embedded node such as image or video +/// An embed node inside of a line in a Quill document. +/// +/// Embed node is a leaf node similar to [Text]. It represents an arbitrary +/// piece of non-textual content embedded into a document, such as, image, +/// horizontal rule, video, or any other object with defined structure, +/// like a tweet, for instance. +/// +/// Embed node's length is always `1` character and it is represented with +/// unicode object replacement character in the document text. +/// +/// Any inline style can be applied to an embed, however this does not +/// necessarily mean the embed will look according to that style. For instance, +/// applying "bold" style to an image gives no effect, while adding a "link" to +/// an image actually makes the image react to user's action. class Embed extends Leaf { Embed(Embeddable data) : super.val(data); + static const kObjectReplacementCharacter = '\uFFFC'; + @override - Embeddable get value => super.value as Embeddable; + Node newInstance() => throw UnimplementedError(); @override - String toPlainText() { - return '\uFFFC'; - } + Embeddable get value => super.value as Embeddable; + /// // Embed nodes are represented as unicode object replacement character in + // plain text. @override - Node newInstance() { - throw UnimplementedError(); - } + String toPlainText() => kObjectReplacementCharacter; } diff --git a/lib/models/documents/nodes/line.dart b/lib/models/documents/nodes/line.dart index f3473ac9..ec933b52 100644 --- a/lib/models/documents/nodes/line.dart +++ b/lib/models/documents/nodes/line.dart @@ -9,6 +9,12 @@ import 'embed.dart'; import 'leaf.dart'; import 'node.dart'; +/// A line of rich text in a Quill document. +/// +/// Line serves as a container for [Leaf]s, like [Text] and [Embed]. +/// +/// When a line contains an embed, it fully occupies the line, no other embeds +/// or text nodes are allowed. class Line extends Container { @override Leaf get defaultChild => Text(); @@ -16,6 +22,7 @@ class Line extends Container { @override int get length => super.length + 1; + /// Returns `true` if this line contains an embedded object. bool get hasEmbed { if (childCount != 1) { return false; @@ -24,6 +31,7 @@ class Line extends Container { return children.single is Embed; } + /// Returns next [Line] or `null` if this is the last line in the document. Line? get nextLine { if (!isLast) { return next is Block ? (next as Block).first as Line? : next as Line?; @@ -40,6 +48,9 @@ class Line extends Container { : parent!.next as Line?; } + @override + Node newInstance() => Line(); + @override Delta toDelta() { final delta = children @@ -67,34 +78,42 @@ class Line extends Container { @override void insert(int index, Object data, Style? style) { if (data is Embeddable) { - _insert(index, data, style); + // We do not check whether this line already has any children here as + // inserting an embed into a line with other text is acceptable from the + // Delta format perspective. + // We rely on heuristic rules to ensure that embeds occupy an entire line. + _insertSafe(index, data, style); return; } final text = data as String; final lineBreak = text.indexOf('\n'); if (lineBreak < 0) { - _insert(index, text, style); + _insertSafe(index, text, style); + // No need to update line or block format since those attributes can only + // be attached to `\n` character and we already know it's not present. return; } final prefix = text.substring(0, lineBreak); - _insert(index, prefix, style); + _insertSafe(index, prefix, style); if (prefix.isNotEmpty) { index += prefix.length; } + // Next line inherits our format. final nextLine = _getNextLine(index); + // Reset our format and unwrap from a block if needed. clearStyle(); - if (parent is Block) { _unwrap(); } + // Now we can apply new format and re-layout. _format(style); - // Continue with the remaining + // Continue with remaining part. final remain = text.substring(lineBreak + 1); nextLine.insert(0, remain, style); } @@ -104,16 +123,20 @@ class Line extends Container { if (style == null) { return; } - final thisLen = length; + final thisLength = length; - final local = math.min(thisLen - index, len!); + final local = math.min(thisLength - index, len!); + // If index is at newline character then this is a line/block style update. + final isLineFormat = (index + local == thisLength) && local == 1; - if (index + local == thisLen && local == 1) { - assert(style.values.every((attr) => attr.scope == AttributeScope.BLOCK)); + if (isLineFormat) { + assert(style.values.every((attr) => attr.scope == AttributeScope.BLOCK), + 'It is not allowed to apply inline attributes to line itself.'); _format(style); } else { + // Otherwise forward to children as it's an inline format update. assert(style.values.every((attr) => attr.scope == AttributeScope.INLINE)); - assert(index + local != thisLen); + assert(index + local != thisLength); super.retain(index, local, style); } @@ -127,35 +150,47 @@ class Line extends Container { @override void delete(int index, int? len) { final local = math.min(length - index, len!); - final deleted = index + local == length; - if (deleted) { + final isLFDeleted = index + local == length; // Line feed + if (isLFDeleted) { + // Our newline character deleted with all style information. clearStyle(); if (local > 1) { + // Exclude newline character from delete range for children. super.delete(index, local - 1); } } else { super.delete(index, local); } - final remain = len - local; - if (remain > 0) { + final remaining = len - local; + if (remaining > 0) { assert(nextLine != null); - nextLine!.delete(0, remain); + nextLine!.delete(0, remaining); } - if (deleted && isNotEmpty) { + if (isLFDeleted && isNotEmpty) { + // Since we lost our line-break and still have child text nodes those must + // migrate to the next line. + + // nextLine might have been unmounted since last assert so we need to + // check again we still have a line after us. assert(nextLine != null); + + // Move remaining children in this line to the next line so that all + // attributes of nextLine are preserved. nextLine!.moveChildToNewParent(this); moveChildToNewParent(nextLine); } - if (deleted) { - final Node p = parent!; + if (isLFDeleted) { + // Now we can remove this line. + final block = parent!; // remember reference before un-linking. unlink(); - p.adjust(); + block.adjust(); } } + /// Formats this line. void _format(Style? newStyle) { if (newStyle == null || newStyle.isEmpty) { return; @@ -165,7 +200,7 @@ class Line extends Container { final blockStyle = newStyle.getBlockExceptHeader(); if (blockStyle == null) { return; - } + } // No block-level changes if (parent is Block) { final parentStyle = (parent as Block).style.getBlockExceptHeader(); @@ -176,14 +211,18 @@ class Line extends Container { final block = Block()..applyAttribute(blockStyle); _wrap(block); block.adjust(); - } + } // else the same style, no-op. } else if (blockStyle.value != null) { + // Only wrap with a new block if this is not an unset final block = Block()..applyAttribute(blockStyle); _wrap(block); block.adjust(); } } + /// Wraps this line with new parent [block]. + /// + /// This line can not be in a [Block] when this method is called. void _wrap(Block block) { assert(parent != null && parent is! Block); insertAfter(block); @@ -191,6 +230,9 @@ class Line extends Container { block.add(this); } + /// Unwraps this line from it's parent [Block]. + /// + /// This method asserts if current [parent] of this line is not a [Block]. void _unwrap() { if (parent is! Block) { throw ArgumentError('Invalid parent'); @@ -242,7 +284,7 @@ class Line extends Container { return line; } - void _insert(int index, Object data, Style? style) { + void _insertSafe(int index, Object data, Style? style) { assert(index == 0 || (index > 0 && index < length)); if (data is String) { @@ -252,46 +294,50 @@ class Line extends Container { } } - if (isNotEmpty) { + if (isEmpty) { + final child = Leaf(data); + add(child); + child.format(style); + } else { final result = queryChild(index, true); result.node!.insert(result.offset, data, style); - return; } - - final child = Leaf(data); - add(child); - child.format(style); - } - - @override - Node newInstance() { - return Line(); } + /// Returns style for specified text range. + /// + /// Only attributes applied to all characters within this range are + /// included in the result. Inline and line level attributes are + /// handled separately, e.g.: + /// + /// - line attribute X is included in the result only if it exists for + /// every line within this range (partially included lines are counted). + /// - inline attribute X is included in the result only if it exists + /// for every character within this range (line-break characters excluded). Style collectStyle(int offset, int len) { final local = math.min(length - offset, len); - var res = Style(); + var result = Style(); final excluded = {}; void _handle(Style style) { - if (res.isEmpty) { + if (result.isEmpty) { excluded.addAll(style.values); } else { - for (final attr in res.values) { + for (final attr in result.values) { if (!style.containsKey(attr.key)) { excluded.add(attr); } } } - final remain = style.removeAll(excluded); - res = res.removeAll(excluded); - res = res.mergeAll(remain); + final remaining = style.removeAll(excluded); + result = result.removeAll(excluded); + result = result.mergeAll(remaining); } final data = queryChild(offset, true); var node = data.node as Leaf?; if (node != null) { - res = res.mergeAll(node.style); + result = result.mergeAll(node.style); var pos = node.length - data.offset; while (!node!.isLast && pos < local) { node = node.next as Leaf?; @@ -300,17 +346,18 @@ class Line extends Container { } } - res = res.mergeAll(style); + result = result.mergeAll(style); if (parent is Block) { final block = parent as Block; - res = res.mergeAll(block.style); + result = result.mergeAll(block.style); } - final remain = len - local; - if (remain > 0) { - _handle(nextLine!.collectStyle(0, remain)); + final remaining = len - local; + if (remaining > 0) { + final rest = nextLine!.collectStyle(0, remaining); + _handle(rest); } - return res; + return result; } } diff --git a/lib/models/documents/nodes/node.dart b/lib/models/documents/nodes/node.dart index ddd1078c..6bb0fb97 100644 --- a/lib/models/documents/nodes/node.dart +++ b/lib/models/documents/nodes/node.dart @@ -6,36 +6,37 @@ import '../style.dart'; import 'container.dart'; import 'line.dart'; -/* node in a document tree */ +/// An abstract node in a document tree. +/// +/// Represents a segment of a Quill document with specified [offset] +/// and [length]. +/// +/// The [offset] property is relative to [parent]. See also [documentOffset] +/// which provides absolute offset of this node within the document. +/// +/// The current parent node is exposed by the [parent] property. abstract class Node extends LinkedListEntry { + /// Current parent of this node. May be null if this node is not mounted. Container? parent; - Style _style = Style(); Style get style => _style; + Style _style = Style(); - void applyAttribute(Attribute attribute) { - _style = _style.merge(attribute); - } - - void applyStyle(Style value) { - _style = _style.mergeAll(value); - } - - void clearStyle() { - _style = Style(); - } - + /// Returns `true` if this node is the first node in the [parent] list. bool get isFirst => list!.first == this; + /// Returns `true` if this node is the last node in the [parent] list. bool get isLast => list!.last == this; + /// Length of this node in characters. int get length; - Node clone() { - return newInstance()..applyStyle(style); - } + Node clone() => newInstance()..applyStyle(style); - int getOffset() { + /// Offset in characters of this node relative to [parent] node. + /// + /// To get offset of this node in the document see [documentOffset]. + int get offset { var offset = 0; if (list == null || isFirst) { @@ -50,16 +51,31 @@ abstract class Node extends LinkedListEntry { return offset; } - int getDocumentOffset() { - final parentOffset = (parent is! Root) ? parent!.getDocumentOffset() : 0; - return parentOffset + getOffset(); + /// Offset in characters of this node in the document. + int get documentOffset { + final parentOffset = (parent is! Root) ? parent!.documentOffset : 0; + return parentOffset + offset; } + /// Returns `true` if this node contains character at specified [offset] in + /// the document. bool containsOffset(int offset) { - final o = getDocumentOffset(); + final o = documentOffset; return o <= offset && offset < o + length; } + void applyAttribute(Attribute attribute) { + _style = _style.merge(attribute); + } + + void applyStyle(Style value) { + _style = _style.mergeAll(value); + } + + void clearStyle() { + _style = Style(); + } + @override void insertBefore(Node entry) { assert(entry.parent == null && parent != null); @@ -81,9 +97,7 @@ abstract class Node extends LinkedListEntry { super.unlink(); } - void adjust() { - // do nothing - } + void adjust() {/* no-op */} /// abstract methods begin @@ -100,11 +114,13 @@ abstract class Node extends LinkedListEntry { void delete(int index, int? len); /// abstract methods end - } -/* Root node of document tree */ +/// Root node of document tree. class Root extends Container> { + @override + Node newInstance() => Root(); + @override Container get defaultChild => Line(); @@ -112,9 +128,4 @@ class Root extends Container> { Delta toDelta() => children .map((child) => child.toDelta()) .fold(Delta(), (a, b) => a.concat(b)); - - @override - Node newInstance() { - return Root(); - } } diff --git a/lib/widgets/editor.dart b/lib/widgets/editor.dart index 3b33211c..4d2ce846 100644 --- a/lib/widgets/editor.dart +++ b/lib/widgets/editor.dart @@ -577,8 +577,7 @@ class RenderEditor extends RenderEditableContainerBox if (textSelection.isCollapsed) { final child = childAtPosition(textSelection.extent); final localPosition = TextPosition( - offset: - textSelection.extentOffset - child.getContainer().getOffset()); + offset: textSelection.extentOffset - child.getContainer().offset); final localOffset = child.getOffsetForCaret(localPosition); final parentData = child.parentData as BoxParentData; return [ @@ -677,7 +676,7 @@ class RenderEditor extends RenderEditableContainerBox assert(_lastTapDownPosition != null); final position = getPositionForOffset(_lastTapDownPosition!); final child = childAtPosition(position); - final nodeOffset = child.getContainer().getOffset(); + final nodeOffset = child.getContainer().offset; final localPosition = TextPosition( offset: position.offset - nodeOffset, affinity: position.affinity, @@ -738,7 +737,7 @@ class RenderEditor extends RenderEditableContainerBox @override TextSelection selectWordAtPosition(TextPosition position) { final child = childAtPosition(position); - final nodeOffset = child.getContainer().getOffset(); + final nodeOffset = child.getContainer().offset; final localPosition = TextPosition( offset: position.offset - nodeOffset, affinity: position.affinity); final localWord = child.getWordBoundary(localPosition); @@ -755,7 +754,7 @@ class RenderEditor extends RenderEditableContainerBox @override TextSelection selectLineAtPosition(TextPosition position) { final child = childAtPosition(position); - final nodeOffset = child.getContainer().getOffset(); + final nodeOffset = child.getContainer().offset; final localPosition = TextPosition( offset: position.offset - nodeOffset, affinity: position.affinity); final localLineRange = child.getLineBoundary(localPosition); @@ -810,8 +809,8 @@ class RenderEditor extends RenderEditableContainerBox @override double preferredLineHeight(TextPosition position) { final child = childAtPosition(position); - return child.preferredLineHeight(TextPosition( - offset: position.offset - child.getContainer().getOffset())); + return child.preferredLineHeight( + TextPosition(offset: position.offset - child.getContainer().offset)); } @override @@ -823,7 +822,7 @@ class RenderEditor extends RenderEditableContainerBox final localOffset = local - parentData.offset; final localPosition = child.getPositionForOffset(localOffset); return TextPosition( - offset: localPosition.offset + child.getContainer().getOffset(), + offset: localPosition.offset + child.getContainer().offset, affinity: localPosition.affinity, ); } @@ -842,8 +841,7 @@ class RenderEditor extends RenderEditableContainerBox final caretTop = endpoint.point.dy - child.preferredLineHeight(TextPosition( - offset: - selection.extentOffset - child.getContainer().getOffset())) - + offset: selection.extentOffset - child.getContainer().offset)) - kMargin + offsetInViewport; final caretBottom = endpoint.point.dy + kMargin + offsetInViewport; diff --git a/lib/widgets/raw_editor.dart b/lib/widgets/raw_editor.dart index 72422991..0ae9e82d 100644 --- a/lib/widgets/raw_editor.dart +++ b/lib/widgets/raw_editor.dart @@ -209,8 +209,7 @@ class RawEditorState extends EditorState final child = getRenderEditor()!.childAtPosition(originPosition); final localPosition = TextPosition( - offset: - originPosition.offset - child.getContainer().getDocumentOffset()); + offset: originPosition.offset - child.getContainer().documentOffset); var position = upKey ? child.getPositionAbove(localPosition) @@ -231,12 +230,12 @@ class RawEditorState extends EditorState .dy); final siblingPosition = sibling.getPositionForOffset(finalOffset); position = TextPosition( - offset: sibling.getContainer().getDocumentOffset() + - siblingPosition.offset); + offset: + sibling.getContainer().documentOffset + siblingPosition.offset); } } else { position = TextPosition( - offset: child.getContainer().getDocumentOffset() + position.offset); + offset: child.getContainer().documentOffset + position.offset); } if (position.offset == newSelection.extentOffset) { diff --git a/lib/widgets/text_block.dart b/lib/widgets/text_block.dart index 25ea8d9c..1c81f1ac 100644 --- a/lib/widgets/text_block.dart +++ b/lib/widgets/text_block.dart @@ -312,12 +312,12 @@ class RenderEditableTextBlock extends RenderEditableContainerBox TextRange getLineBoundary(TextPosition position) { final child = childAtPosition(position); final rangeInChild = child.getLineBoundary(TextPosition( - offset: position.offset - child.getContainer().getOffset(), + offset: position.offset - child.getContainer().offset, affinity: position.affinity, )); return TextRange( - start: rangeInChild.start + child.getContainer().getOffset(), - end: rangeInChild.end + child.getContainer().getOffset(), + start: rangeInChild.start + child.getContainer().offset, + end: rangeInChild.end + child.getContainer().offset, ); } @@ -325,7 +325,7 @@ class RenderEditableTextBlock extends RenderEditableContainerBox Offset getOffsetForCaret(TextPosition position) { final child = childAtPosition(position); return child.getOffsetForCaret(TextPosition( - offset: position.offset - child.getContainer().getOffset(), + offset: position.offset - child.getContainer().offset, affinity: position.affinity, )) + (child.parentData as BoxParentData).offset; @@ -338,7 +338,7 @@ class RenderEditableTextBlock extends RenderEditableContainerBox final localPosition = child.getPositionForOffset(offset - parentData.offset); return TextPosition( - offset: localPosition.offset + child.getContainer().getOffset(), + offset: localPosition.offset + child.getContainer().offset, affinity: localPosition.affinity, ); } @@ -346,7 +346,7 @@ class RenderEditableTextBlock extends RenderEditableContainerBox @override TextRange getWordBoundary(TextPosition position) { final child = childAtPosition(position); - final nodeOffset = child.getContainer().getOffset(); + final nodeOffset = child.getContainer().offset; final childWord = child .getWordBoundary(TextPosition(offset: position.offset - nodeOffset)); return TextRange( @@ -360,12 +360,11 @@ class RenderEditableTextBlock extends RenderEditableContainerBox assert(position.offset < getContainer().length); final child = childAtPosition(position); - final childLocalPosition = TextPosition( - offset: position.offset - child.getContainer().getOffset()); + final childLocalPosition = + TextPosition(offset: position.offset - child.getContainer().offset); final result = child.getPositionAbove(childLocalPosition); if (result != null) { - return TextPosition( - offset: result.offset + child.getContainer().getOffset()); + return TextPosition(offset: result.offset + child.getContainer().offset); } final sibling = childBefore(child); @@ -379,7 +378,7 @@ class RenderEditableTextBlock extends RenderEditableContainerBox final testOffset = sibling.getOffsetForCaret(testPosition); final finalOffset = Offset(caretOffset.dx, testOffset.dy); return TextPosition( - offset: sibling.getContainer().getOffset() + + offset: sibling.getContainer().offset + sibling.getPositionForOffset(finalOffset).offset); } @@ -388,12 +387,11 @@ class RenderEditableTextBlock extends RenderEditableContainerBox assert(position.offset < getContainer().length); final child = childAtPosition(position); - final childLocalPosition = TextPosition( - offset: position.offset - child.getContainer().getOffset()); + final childLocalPosition = + TextPosition(offset: position.offset - child.getContainer().offset); final result = child.getPositionBelow(childLocalPosition); if (result != null) { - return TextPosition( - offset: result.offset + child.getContainer().getOffset()); + return TextPosition(offset: result.offset + child.getContainer().offset); } final sibling = childAfter(child); @@ -405,15 +403,15 @@ class RenderEditableTextBlock extends RenderEditableContainerBox final testOffset = sibling.getOffsetForCaret(const TextPosition(offset: 0)); final finalOffset = Offset(caretOffset.dx, testOffset.dy); return TextPosition( - offset: sibling.getContainer().getOffset() + + offset: sibling.getContainer().offset + sibling.getPositionForOffset(finalOffset).offset); } @override double preferredLineHeight(TextPosition position) { final child = childAtPosition(position); - return child.preferredLineHeight(TextPosition( - offset: position.offset - child.getContainer().getOffset())); + return child.preferredLineHeight( + TextPosition(offset: position.offset - child.getContainer().offset)); } @override diff --git a/lib/widgets/text_line.dart b/lib/widgets/text_line.dart index 43cc6969..3106a08d 100644 --- a/lib/widgets/text_line.dart +++ b/lib/widgets/text_line.dart @@ -402,8 +402,8 @@ class RenderEditableTextLine extends RenderEditableBox { } bool containsTextSelection() { - return line.getDocumentOffset() <= textSelection.end && - textSelection.start <= line.getDocumentOffset() + line.length - 1; + return line.documentOffset <= textSelection.end && + textSelection.start <= line.documentOffset + line.length - 1; } bool containsCursor() { @@ -735,8 +735,8 @@ class RenderEditableTextLine extends RenderEditableBox { final parentData = _body!.parentData as BoxParentData; final effectiveOffset = offset + parentData.offset; if (enableInteractiveSelection && - line.getDocumentOffset() <= textSelection.end && - textSelection.start <= line.getDocumentOffset() + line.length - 1) { + line.documentOffset <= textSelection.end && + textSelection.start <= line.documentOffset + line.length - 1) { final local = localSelection(line, textSelection, false); _selectedRects ??= _body!.getBoxesForSelection( local, @@ -772,7 +772,7 @@ class RenderEditableTextLine extends RenderEditableBox { void _paintCursor(PaintingContext context, Offset effectiveOffset) { final position = TextPosition( - offset: textSelection.extentOffset - line.getDocumentOffset(), + offset: textSelection.extentOffset - line.documentOffset, affinity: textSelection.base.affinity, ); _cursorPainter.paint(context.canvas, effectiveOffset, position); diff --git a/lib/widgets/text_selection.dart b/lib/widgets/text_selection.dart index fe28741b..dbf9d856 100644 --- a/lib/widgets/text_selection.dart +++ b/lib/widgets/text_selection.dart @@ -12,10 +12,10 @@ import '../models/documents/nodes/node.dart'; import 'editor.dart'; TextSelection localSelection(Node node, TextSelection selection, fromParent) { - final base = fromParent ? node.getOffset() : node.getDocumentOffset(); + final base = fromParent ? node.offset : node.documentOffset; assert(base <= selection.end && selection.start <= base + node.length - 1); - final offset = fromParent ? node.getOffset() : node.getDocumentOffset(); + final offset = fromParent ? node.offset : node.documentOffset; return selection.copyWith( baseOffset: math.max(selection.start - offset, 0), extentOffset: math.min(selection.end - offset, node.length - 1));