]> git.giacomolli.uk - onechat/blobdiff - view.go
Require consent and review for autosquash
[onechat] / view.go
diff --git a/view.go b/view.go
index 1b3ed6ae985b902811ca263d0047a8953800638a..9af1c21476fccfb2d1aa7ba394d8a3120ef2c35b 100644 (file)
--- a/view.go
+++ b/view.go
@@ -88,13 +88,121 @@ func (v *View) Append(tree ViewNodeReader) (bool, error) {
        line := NodeKey{0, v.end}
        _, err := v.load(tree, line)
        if err != nil {
-               return false, err
+               return false, fmt.Errorf("load appended leaf: %w", err)
        }
 
        v.lines = append(v.lines, line)
        v.end++
 
-       return v.fit(tree)
+       merged, err := v.fit(tree)
+       if err != nil {
+               return merged, fmt.Errorf("fit appended view: %w", err)
+       }
+
+       return merged, nil
+}
+
+// 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 {
+                       if err != nil {
+                               return merged, fmt.Errorf("choose merge candidate: %w", err)
+                       }
+
+                       return merged, nil
+               }
+
+               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, fmt.Errorf("load merged parent: %w", 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
+}
+
+// Size returns the sum of the line sizes.
+func (v *View) Size() int {
+       size := 0
+       for _, line := range v.lines {
+               size += v.lineSize(line)
+       }
+
+       return size
+}
+
+// mostDue returns the position of the left line of the most due sibling pair
+// whose parent is built, or -1 when there is no such pair.
+func (v *View) mostDue(tree ViewNodeReader) (int, error) {
+       best := -1
+       bestDue := 0.0
+       for n := 0; n+1 < len(v.lines); n++ {
+               left, right := v.lines[n], v.lines[n+1]
+               if left.index%2 != 0 || right.level != left.level || right.index != left.index+1 {
+                       continue
+               }
+
+               last := nodeEnd(right.level, right.index) - 1
+               due := math.Ldexp(float64(v.end-last), -left.level)
+               if best >= 0 && due <= bestDue {
+                       continue
+               }
+
+               built, err := tree.Built(left.level+1, left.index/2)
+               if err != nil {
+                       return -1, fmt.Errorf("check parent node: %w", err)
+               }
+
+               if built {
+                       best = n
+                       bestDue = due
+               }
+       }
+
+       return best, nil
+}
+
+// lineSize returns the size of a view line.
+func (v *View) lineSize(line NodeKey) int {
+       node, built := v.nodes[line]
+       if !built {
+               return len(viewPending)
+       }
+
+       return node.Size
 }
 
 // Coarsen takes each merge of other, a view of the same messages, and then
@@ -191,76 +299,6 @@ func (v *View) LinesWithout(skip map[int]bool) []string {
        return lines
 }
 
-// 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
-}
-
 // renderLine writes one view line, "id+n|text", and a newline.
 func (v *View) renderLine(text *strings.Builder, line NodeKey) {
        body := viewPending
@@ -277,55 +315,26 @@ func (v *View) renderLine(text *strings.Builder, line NodeKey) {
        text.WriteByte('\n')
 }
 
-// Size returns the sum of the line sizes.
-func (v *View) Size() int {
-       size := 0
-       for _, line := range v.lines {
-               size += v.lineSize(line)
-       }
-
-       return size
-}
-
-// mostDue returns the position of the left line of the most due sibling pair
-// whose parent is built, or -1 when there is no such pair.
-func (v *View) mostDue(tree ViewNodeReader) (int, error) {
-       best := -1
-       bestDue := 0.0
-       for n := 0; n+1 < len(v.lines); n++ {
-               left, right := v.lines[n], v.lines[n+1]
-               if left.index%2 != 0 || right.level != left.level || right.index != left.index+1 {
-                       continue
-               }
+// 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
 
-               last := nodeEnd(right.level, right.index) - 1
-               due := math.Ldexp(float64(v.end-last), -left.level)
-               if best >= 0 && due <= bestDue {
-                       continue
-               }
+       text.WriteString("<chat>\n")
 
-               built, err := tree.Built(left.level+1, left.index/2)
-               if err != nil {
-                       return -1, fmt.Errorf("check parent node: %w", err)
+       for _, line := range v.lines {
+               _, built := v.nodes[line]
+               if !built || nodeEnd(line.level, line.index) > end {
+                       break
                }
 
-               if built {
-                       best = n
-                       bestDue = due
-               }
+               v.renderLine(&text, line)
        }
 
-       return best, nil
-}
-
-// lineSize returns the size of a view line.
-func (v *View) lineSize(line NodeKey) int {
-       node, built := v.nodes[line]
-       if !built {
-               return len(viewPending)
-       }
+       text.WriteString("</chat>")
 
-       return node.Size
+       return text.String()
 }
 
 // restoreView returns the view of state with the limits high and low. Each line
@@ -341,7 +350,7 @@ func restoreView(state ViewState, high, low int, tree ViewNodeReader) (*View, er
 
                built, err := v.load(tree, line)
                if err != nil {
-                       return nil, err
+                       return nil, fmt.Errorf("load restored view node: %w", err)
                }
 
                if line.level > 0 && !built {