+// Context returns the lines of the view before message end as the model reads
+// them. It stops at the first line that is not built, so a compaction never
+// sees a pending mark or a part of a message.
+func (v *View) Context(end int) string {
+ var text strings.Builder
+
+ text.WriteString("<chat>\n")
+
+ for _, line := range v.lines {
+ _, built := v.nodes[line]
+ if !built || nodeEnd(line.level, line.index) > end {
+ break
+ }
+
+ v.renderLine(&text, line)
+ }
+
+ text.WriteString("</chat>")
+
+ return text.String()
+}
+
+// load keeps the node of line when it is built, and tells if it is.
+func (v *View) load(tree ViewNodeReader, line NodeKey) (bool, error) {
+ node, built, err := tree.Node(line.level, line.index)
+ if err != nil || !built {
+ return false, err
+ }
+
+ v.nodes[line] = node
+
+ return true, nil
+}
+
+// fit merges the most due pairs while a batch runs, and tells if a merge
+// changed the view.
+func (v *View) fit(tree ViewNodeReader) (bool, error) {
+ size := v.Size()
+ if size > v.high {
+ v.merging = true
+ }
+
+ merged := false
+ for v.merging && size > v.low {
+ best, err := v.mostDue(tree)
+ if err != nil || best < 0 {
+ return merged, err
+ }
+
+ left, right := v.lines[best], v.lines[best+1]
+ parent := NodeKey{left.level + 1, left.index / 2}
+ _, err = v.load(tree, parent)
+ if err != nil {
+ return merged, err
+ }
+
+ size += v.lineSize(parent) - v.lineSize(left) - v.lineSize(right)
+ delete(v.nodes, left)
+ delete(v.nodes, right)
+
+ v.lines[best] = parent
+ v.lines = append(v.lines[:best+1], v.lines[best+2:]...)
+ merged = true
+ }
+
+ v.merging = false
+
+ return merged, nil
+}
+