From 90b0bc0ae9b47255e0075a06d36a2c2b5c1cfd34 Mon Sep 17 00:00:00 2001 From: bloeys Date: Wed, 20 Jul 2022 02:18:36 +0400 Subject: [PATCH] Scroll by lines that respects ring buf+iterator prevN/nextN+it.Len --- main.go | 65 +++++++++++++++++++++++++++++------------------ ring/ring.go | 61 ++++++++++++++++++++++++++++++++++++++++++++ ring/ring_test.go | 22 ++++++++++++++++ 3 files changed, 123 insertions(+), 25 deletions(-) diff --git a/main.go b/main.go index 1ee16e4..9be417a 100755 --- a/main.go +++ b/main.go @@ -284,11 +284,9 @@ func (p *program) MainUpdate() { var newPosNewLines int64 w, _ := p.GridSize() if mouseWheelYNorm < 0 { - // @TODO This is wrong because it deals with data as a normal array while its a ring buffer - - newPosNewLines, _ = find_n_lines_index(p.textBuf.Data, p.scrollPos, p.scrollSpd*mouseWheelYNorm-1, int64(w)) + newPosNewLines, _ = find_n_lines_index_iterator(p.textBuf.Iterator(), p.scrollPos, p.scrollSpd*mouseWheelYNorm-1, int64(w)) } else { - newPosNewLines, _ = find_n_lines_index(p.textBuf.Data, p.scrollPos, p.scrollSpd*mouseWheelYNorm, int64(w)) + newPosNewLines, _ = find_n_lines_index_iterator(p.textBuf.Iterator(), p.scrollPos, p.scrollSpd*mouseWheelYNorm, int64(w)) } p.scrollPos = clamp(newPosNewLines, 0, p.textBuf.Len) @@ -831,22 +829,36 @@ func FindNthOrLastIndex[T comparable](arr []T, x T, startIndex, n int64) (lastIn // // Note: When moving backwards from the start of the line, the first char will be a new line (e.g. \n), so the first counted line is not a full line // but only a single rune. So in most cases to get '-n' lines backwards you should request '-n-1' lines. -func find_n_lines_index(arr []byte, startIndex, n, charsPerLine int64) (lastIndex, lastSize int64) { +func find_n_lines_index_iterator(it ring.Iterator[byte], startIndex, n, charsPerLine int64) (newIndex, newSize int64) { + + // If nothing changes (e.g. already at end of iterator) then we will stay at the same place + + done := false + read := 0 + bytesToKeep := 0 + buf := make([]byte, 4) + it.GotoIndex(startIndex) + + // @Note we should ignore zero width glyphs + // @Note is this better in glyphs package? + bytesSeen := int64(0) + charsSeenThisLine := int64(0) - lastIndex = -1 - lastSize = 0 if n >= 0 { - // @Note we should ignore zero width glyphs - // @Note is this better in glyphs package? - bytesSeen := int64(0) - arrSize := int64(len(arr)) - charsSeenThisLine := int64(0) - for startIndex+bytesSeen < arrSize { - r, size := utf8.DecodeRune(arr[startIndex+bytesSeen:]) + // If nothing changes (e.g. already at end of iterator) then we will stay at the same place + newIndex = startIndex + + for !done || bytesToKeep > 0 { + + read, done = it.NextN(buf[bytesToKeep:], 4) + + r, size := utf8.DecodeRune(buf[:bytesToKeep+read]) if r == utf8.RuneError { break } + bytesToKeep += read - size + copy(buf, buf[size:size+bytesToKeep]) charsSeenThisLine++ bytesSeen += int64(size) @@ -855,8 +867,8 @@ func find_n_lines_index(arr []byte, startIndex, n, charsPerLine int64) (lastInde if charsSeenThisLine == charsPerLine || r == '\n' { charsSeenThisLine = 0 - lastSize = int64(size) - lastIndex = startIndex + bytesSeen + newSize = int64(size) + newIndex = startIndex + bytesSeen n-- if n <= 0 { @@ -867,14 +879,16 @@ func find_n_lines_index(arr []byte, startIndex, n, charsPerLine int64) (lastInde } else { - bytesSeen := int64(0) - charsSeenThisLine := int64(0) - for startIndex-bytesSeen > 0 { + for !done || bytesToKeep > 0 { - r, size := utf8.DecodeLastRune(arr[:startIndex-bytesSeen+1]) + read, done = it.PrevN(buf[bytesToKeep:], 4) + + r, size := utf8.DecodeRune(buf[:bytesToKeep+read]) if r == utf8.RuneError { break } + bytesToKeep += read - size + copy(buf, buf[size:size+bytesToKeep]) charsSeenThisLine++ bytesSeen += int64(size) @@ -883,8 +897,8 @@ func find_n_lines_index(arr []byte, startIndex, n, charsPerLine int64) (lastInde if charsSeenThisLine == charsPerLine || r == '\n' { charsSeenThisLine = 0 - lastSize = int64(size) - lastIndex = startIndex - bytesSeen + 1 + lastSize + newSize = int64(size) + newIndex = startIndex - bytesSeen + newSize n++ if n >= 0 { @@ -893,11 +907,12 @@ func find_n_lines_index(arr []byte, startIndex, n, charsPerLine int64) (lastInde } } - // Handle reaching beginning before finding nth line + // If we reached beginning of buffer before finding a new line then newIndex is zero if startIndex-bytesSeen == 0 { - return 0, 0 + newIndex = 0 + newSize = 0 } } - return lastIndex, lastSize + return newIndex, newSize } diff --git a/ring/ring.go b/ring/ring.go index 4daf254..8b052d7 100755 --- a/ring/ring.go +++ b/ring/ring.go @@ -143,6 +143,10 @@ type Iterator[T any] struct { InV1 bool } +func (it *Iterator[T]) Len() int64 { + return int64(len(it.V1) + len(it.V2)) +} + // Next returns the value at Iterator.Curr and done=false // // If there are no more values to return the default value is returned for v and done=true @@ -198,6 +202,63 @@ func (it *Iterator[T]) Prev() (v T, done bool) { return v, false } +// NextN calls Next() up to n times and places the result in the passed buffer. +// 'read' is the actual number of elements put in the buffer. +// We might not be able to put 'n' elements because the buffer is too small or because there aren't enough remaining elements +func (it *Iterator[T]) NextN(buf []T, n int) (read int, done bool) { + + if n > len(buf) { + n = len(buf) + } + + var v T + for v, done = it.Next(); !done; v, done = it.Next() { + + buf[read] = v + read++ + + // We must break inside the loop not in the 'for' check because + // if we check part of the loop and break before done=true we will waste the last + // value + n-- + if n == 0 { + break + } + } + + return read, done +} + +// PrevN calls Prev() up to n times and places the result in the passed buffer in the order they are read from Prev(). +// That is, the first Prev() call is put into index 0, second Prev() call is in index 1, and so on, +// similar to if you were doing a reverse loop on an array. +// +// 'read' is the actual number of elements put in the buffer. +// We might not be able to put 'n' elements because the buffer is too small or because there aren't enough remaining elements +func (it *Iterator[T]) PrevN(buf []T, n int) (read int, done bool) { + + if n > len(buf) { + n = len(buf) + } + + var v T + for v, done = it.Prev(); !done; v, done = it.Prev() { + + buf[read] = v + read++ + + // We must break inside the loop not in the 'for' check because + // if we check part of the loop and break before done=true we will waste the last + // value + n-- + if n == 0 { + break + } + } + + return read, done +} + // GotoStart adjusts the iterator such that the following Next() call returns the value at index=0 // and the next Prev() call returns done=true func (it *Iterator[T]) GotoStart() { diff --git a/ring/ring_test.go b/ring/ring_test.go index 5351091..5307e19 100755 --- a/ring/ring_test.go +++ b/ring/ring_test.go @@ -176,6 +176,28 @@ func TestIterator(t *testing.T) { got = append(got, v) } CheckArr(t, ans, got) + + // NextN + got = make([]int, 2) + ans = []int{3, 4} + it.GotoStart() + it.NextN(got, 2) + CheckArr(t, ans, got) + + ans = []int{5, 6} + it.NextN(got, 2) + CheckArr(t, ans, got) + + // PrevN + got = make([]int, 2) + ans = []int{6, 5} + it.GotoEnd() + it.PrevN(got, 2) + CheckArr(t, ans, got) + + ans = []int{4, 3} + it.PrevN(got, 2) + CheckArr(t, ans, got) } func Check[T comparable](t *testing.T, expected, got T) {