summaryrefslogtreecommitdiff
path: root/vendor/github.com/charmbracelet/x/cellbuf/hashmap.go
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-22 17:35:49 -0600
committermo khan <mo@mokhan.ca>2025-07-22 17:35:49 -0600
commit20ef0d92694465ac86b550df139e8366a0a2b4fa (patch)
tree3f14589e1ce6eb9306a3af31c3a1f9e1af5ed637 /vendor/github.com/charmbracelet/x/cellbuf/hashmap.go
parent44e0d272c040cdc53a98b9f1dc58ae7da67752e6 (diff)
feat: connect to spicedb
Diffstat (limited to 'vendor/github.com/charmbracelet/x/cellbuf/hashmap.go')
-rw-r--r--vendor/github.com/charmbracelet/x/cellbuf/hashmap.go301
1 files changed, 301 insertions, 0 deletions
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/hashmap.go b/vendor/github.com/charmbracelet/x/cellbuf/hashmap.go
new file mode 100644
index 0000000..0d25b54
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/hashmap.go
@@ -0,0 +1,301 @@
+package cellbuf
+
+import (
+ "github.com/charmbracelet/x/ansi"
+)
+
+// hash returns the hash value of a [Line].
+func hash(l Line) (h uint64) {
+ for _, c := range l {
+ var r rune
+ if c == nil {
+ r = ansi.SP
+ } else {
+ r = c.Rune
+ }
+ h += (h << 5) + uint64(r)
+ }
+ return
+}
+
+// hashmap represents a single [Line] hash.
+type hashmap struct {
+ value uint64
+ oldcount, newcount int
+ oldindex, newindex int
+}
+
+// The value used to indicate lines created by insertions and scrolls.
+const newIndex = -1
+
+// updateHashmap updates the hashmap with the new hash value.
+func (s *Screen) updateHashmap() {
+ height := s.newbuf.Height()
+ if len(s.oldhash) >= height && len(s.newhash) >= height {
+ // rehash changed lines
+ for i := 0; i < height; i++ {
+ _, ok := s.touch[i]
+ if ok {
+ s.oldhash[i] = hash(s.curbuf.Line(i))
+ s.newhash[i] = hash(s.newbuf.Line(i))
+ }
+ }
+ } else {
+ // rehash all
+ if len(s.oldhash) != height {
+ s.oldhash = make([]uint64, height)
+ }
+ if len(s.newhash) != height {
+ s.newhash = make([]uint64, height)
+ }
+ for i := 0; i < height; i++ {
+ s.oldhash[i] = hash(s.curbuf.Line(i))
+ s.newhash[i] = hash(s.newbuf.Line(i))
+ }
+ }
+
+ s.hashtab = make([]hashmap, height*2)
+ for i := 0; i < height; i++ {
+ hashval := s.oldhash[i]
+
+ // Find matching hash or empty slot
+ idx := 0
+ for idx < len(s.hashtab) && s.hashtab[idx].value != 0 {
+ if s.hashtab[idx].value == hashval {
+ break
+ }
+ idx++
+ }
+
+ s.hashtab[idx].value = hashval // in case this is a new hash
+ s.hashtab[idx].oldcount++
+ s.hashtab[idx].oldindex = i
+ }
+ for i := 0; i < height; i++ {
+ hashval := s.newhash[i]
+
+ // Find matching hash or empty slot
+ idx := 0
+ for idx < len(s.hashtab) && s.hashtab[idx].value != 0 {
+ if s.hashtab[idx].value == hashval {
+ break
+ }
+ idx++
+ }
+
+ s.hashtab[idx].value = hashval // in case this is a new hash
+ s.hashtab[idx].newcount++
+ s.hashtab[idx].newindex = i
+
+ s.oldnum[i] = newIndex // init old indices slice
+ }
+
+ // Mark line pair corresponding to unique hash pairs.
+ for i := 0; i < len(s.hashtab) && s.hashtab[i].value != 0; i++ {
+ hsp := &s.hashtab[i]
+ if hsp.oldcount == 1 && hsp.newcount == 1 && hsp.oldindex != hsp.newindex {
+ s.oldnum[hsp.newindex] = hsp.oldindex
+ }
+ }
+
+ s.growHunks()
+
+ // Eliminate bad or impossible shifts. This includes removing those hunks
+ // which could not grow because of conflicts, as well those which are to be
+ // moved too far, they are likely to destroy more than carry.
+ for i := 0; i < height; {
+ var start, shift, size int
+ for i < height && s.oldnum[i] == newIndex {
+ i++
+ }
+ if i >= height {
+ break
+ }
+ start = i
+ shift = s.oldnum[i] - i
+ i++
+ for i < height && s.oldnum[i] != newIndex && s.oldnum[i]-i == shift {
+ i++
+ }
+ size = i - start
+ if size < 3 || size+min(size/8, 2) < abs(shift) {
+ for start < i {
+ s.oldnum[start] = newIndex
+ start++
+ }
+ }
+ }
+
+ // After clearing invalid hunks, try grow the rest.
+ s.growHunks()
+}
+
+// scrollOldhash
+func (s *Screen) scrollOldhash(n, top, bot int) {
+ if len(s.oldhash) == 0 {
+ return
+ }
+
+ size := bot - top + 1 - abs(n)
+ if n > 0 {
+ // Move existing hashes up
+ copy(s.oldhash[top:], s.oldhash[top+n:top+n+size])
+ // Recalculate hashes for newly shifted-in lines
+ for i := bot; i > bot-n; i-- {
+ s.oldhash[i] = hash(s.curbuf.Line(i))
+ }
+ } else {
+ // Move existing hashes down
+ copy(s.oldhash[top-n:], s.oldhash[top:top+size])
+ // Recalculate hashes for newly shifted-in lines
+ for i := top; i < top-n; i++ {
+ s.oldhash[i] = hash(s.curbuf.Line(i))
+ }
+ }
+}
+
+func (s *Screen) growHunks() {
+ var (
+ backLimit int // limits for cells to fill
+ backRefLimit int // limit for references
+ i int
+ nextHunk int
+ )
+
+ height := s.newbuf.Height()
+ for i < height && s.oldnum[i] == newIndex {
+ i++
+ }
+ for ; i < height; i = nextHunk {
+ var (
+ forwardLimit int
+ forwardRefLimit int
+ end int
+ start = i
+ shift = s.oldnum[i] - i
+ )
+
+ // get forward limit
+ i = start + 1
+ for i < height &&
+ s.oldnum[i] != newIndex &&
+ s.oldnum[i]-i == shift {
+ i++
+ }
+
+ end = i
+ for i < height && s.oldnum[i] == newIndex {
+ i++
+ }
+
+ nextHunk = i
+ forwardLimit = i
+ if i >= height || s.oldnum[i] >= i {
+ forwardRefLimit = i
+ } else {
+ forwardRefLimit = s.oldnum[i]
+ }
+
+ i = start - 1
+
+ // grow back
+ if shift < 0 {
+ backLimit = backRefLimit + (-shift)
+ }
+ for i >= backLimit {
+ if s.newhash[i] == s.oldhash[i+shift] ||
+ s.costEffective(i+shift, i, shift < 0) {
+ s.oldnum[i] = i + shift
+ } else {
+ break
+ }
+ i--
+ }
+
+ i = end
+ // grow forward
+ if shift > 0 {
+ forwardLimit = forwardRefLimit - shift
+ }
+ for i < forwardLimit {
+ if s.newhash[i] == s.oldhash[i+shift] ||
+ s.costEffective(i+shift, i, shift > 0) {
+ s.oldnum[i] = i + shift
+ } else {
+ break
+ }
+ i++
+ }
+
+ backLimit = i
+ backRefLimit = backLimit
+ if shift > 0 {
+ backRefLimit += shift
+ }
+ }
+}
+
+// costEffective returns true if the cost of moving line 'from' to line 'to' seems to be
+// cost effective. 'blank' indicates whether the line 'to' would become blank.
+func (s *Screen) costEffective(from, to int, blank bool) bool {
+ if from == to {
+ return false
+ }
+
+ newFrom := s.oldnum[from]
+ if newFrom == newIndex {
+ newFrom = from
+ }
+
+ // On the left side of >= is the cost before moving. On the right side --
+ // cost after moving.
+
+ // Calculate costs before moving.
+ var costBeforeMove int
+ if blank {
+ // Cost of updating blank line at destination.
+ costBeforeMove = s.updateCostBlank(s.newbuf.Line(to))
+ } else {
+ // Cost of updating exiting line at destination.
+ costBeforeMove = s.updateCost(s.curbuf.Line(to), s.newbuf.Line(to))
+ }
+
+ // Add cost of updating source line
+ costBeforeMove += s.updateCost(s.curbuf.Line(newFrom), s.newbuf.Line(from))
+
+ // Calculate costs after moving.
+ var costAfterMove int
+ if newFrom == from {
+ // Source becomes blank after move
+ costAfterMove = s.updateCostBlank(s.newbuf.Line(from))
+ } else {
+ // Source gets updated from another line
+ costAfterMove = s.updateCost(s.curbuf.Line(newFrom), s.newbuf.Line(from))
+ }
+
+ // Add cost of moving source line to destination
+ costAfterMove += s.updateCost(s.curbuf.Line(from), s.newbuf.Line(to))
+
+ // Return true if moving is cost effective (costs less or equal)
+ return costBeforeMove >= costAfterMove
+}
+
+func (s *Screen) updateCost(from, to Line) (cost int) {
+ var fidx, tidx int
+ for i := s.newbuf.Width() - 1; i > 0; i, fidx, tidx = i-1, fidx+1, tidx+1 {
+ if !cellEqual(from.At(fidx), to.At(tidx)) {
+ cost++
+ }
+ }
+ return
+}
+
+func (s *Screen) updateCostBlank(to Line) (cost int) {
+ var tidx int
+ for i := s.newbuf.Width() - 1; i > 0; i, tidx = i-1, tidx+1 {
+ if !cellEqual(nil, to.At(tidx)) {
+ cost++
+ }
+ }
+ return
+}