chatHigh = 128000
chatLow = 64000
+ // compactionHigh and compactionLow are the limits of the compaction
+ // view in bytes.
+ compactionHigh = 32000
+ compactionLow = 16000
+
// pending is the text of a line that is not built yet.
pending = "(not summarized yet: zoom it)"
)
}
// Append adds the leaf of the next message as a line and merges when the view
-// is too large.
-func (v *View) Append(tree nodeReader) {
+// is too large. It tells if a merge changed the view.
+func (v *View) Append(tree nodeReader) bool {
v.lines = append(v.lines, nodeKey{0, v.end})
v.end++
- v.fit(tree)
+
+ return v.fit(tree)
}
-func (v *View) fit(tree nodeReader) {
+// fit merges the most due pairs while a batch runs, and tells if a merge
+// changed the view.
+func (v *View) fit(tree nodeReader) bool {
size := v.Size(tree)
if size > v.high {
v.merging = true
}
+ merged := false
for v.merging && size > v.low {
best := v.mostDue(tree)
if best < 0 {
- return
+ return merged
}
left, right := v.lines[best], v.lines[best+1]
size += lineSize(tree, parent) - lineSize(tree, left) - lineSize(tree, right)
v.lines[best] = parent
v.lines = append(v.lines[:best+1], v.lines[best+2:]...)
+ merged = true
}
v.merging = false
+
+ return merged
+}
+
+// Coarsen takes each merge of other, a view of the same messages, and then
+// merges in one batch until the size is at most low. Each line of the result is
+// the coarser of the two lines that start at its first message. Dyadic ranges
+// either nest or do not overlap, so the result covers the messages without a
+// gap.
+func (v *View) Coarsen(other *View, tree nodeReader) {
+ lines := make([]nodeKey, 0, len(v.lines))
+ mine, theirs := 0, 0
+ for start := 0; start < v.end; {
+ line := v.lines[mine]
+ if other.lines[theirs].level > line.level {
+ line = other.lines[theirs]
+ }
+
+ lines = append(lines, line)
+ start = nodeEnd(line.level, line.index)
+ for mine < len(v.lines) && v.lines[mine].index<<v.lines[mine].level < start {
+ mine++
+ }
+
+ for theirs < len(other.lines) && other.lines[theirs].index<<other.lines[theirs].level < start {
+ theirs++
+ }
+ }
+
+ v.lines = lines
+ v.merging = true
+ v.fit(tree)
}
// mostDue returns the position of the left line of the most due sibling pair
return text.String()
}
+// 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(tree nodeReader, end int) string {
+ var text strings.Builder
+ text.WriteString("<chat>\n")
+ for _, line := range v.lines {
+ _, built := tree.Node(line.level, line.index)
+ if !built || nodeEnd(line.level, line.index) > end {
+ break
+ }
+
+ renderLine(&text, tree, line)
+ }
+
+ text.WriteString("</chat>")
+
+ return text.String()
+}
+
// lineSize returns the size of a view line.
func lineSize(tree nodeReader, line nodeKey) int {
node, built := tree.Node(line.level, line.index)