From 10372dd743c24eb48146211f079e652944085fb6 Mon Sep 17 00:00:00 2001 From: bloeys Date: Sun, 24 Jul 2022 20:56:20 +0400 Subject: [PATCH] Fix paragraph viewing bugs+indexing helpers for ring buffer --- main.go | 153 ++++++++++++++++++++++++---------------------- ring/ring.go | 51 +++++++++++++++- ring/ring_test.go | 28 ++++----- 3 files changed, 141 insertions(+), 91 deletions(-) diff --git a/main.go b/main.go index a8ab6ab..6142e8f 100755 --- a/main.go +++ b/main.go @@ -49,11 +49,12 @@ type Cmd struct { Stderr io.ReadCloser } -type Line struct { +// Para represents a paragraph, a line of text between two new lines +type Para struct { StartIndex, EndIndex uint64 } -func (l *Line) Size() uint64 { +func (l *Para) Size() uint64 { size := l.EndIndex - l.StartIndex return size } @@ -72,10 +73,8 @@ type program struct { gridMesh *meshes.Mesh gridMat *materials.Material - CurrLine Line - // CurrLineValid bool - - Lines *ring.Buffer[Line] + CurrPara Para + Paras *ring.Buffer[Para] textBuf *ring.Buffer[byte] textBufMutex sync.Mutex @@ -143,7 +142,7 @@ func main() { imguiInfo: nmageimgui.NewImGUI(), FontSize: 40, - Lines: ring.NewBuffer[Line](defaultTextBufSize), + Paras: ring.NewBuffer[Para](defaultTextBufSize), textBuf: ring.NewBuffer[byte](defaultTextBufSize), @@ -263,34 +262,6 @@ func (p *program) Update() { p.MainUpdate() } -func (p *program) WriteToTextBuf(text []byte) { - // This is locked because running cmds are potentially writing to it same time we are - p.textBufMutex.Lock() - p.ParseLines(text) - p.textBuf.Write(text...) - p.textBufMutex.Unlock() - - // @Todo we need better handling here - p.scrollPos = clamp(p.Lines.Len-p.CellCountY+3, 0, p.Lines.Len) -} - -func (p *program) WriteToCmdBuf(text []rune) { - - delta := int64(len(text)) - newHeadPos := p.cmdBufLen + delta - if newHeadPos <= defaultCmdBufSize { - - copy(p.cmdBuf[p.cursorCharIndex+delta:], p.cmdBuf[p.cursorCharIndex:]) - copy(p.cmdBuf[p.cursorCharIndex:], text) - - p.cursorCharIndex += delta - p.cmdBufLen = newHeadPos - return - } - - assert.T(false, "Circular buffer not implemented for cmd buf") -} - var sepLinePos = gglm.NewVec3(0, 0, 0) func (p *program) MainUpdate() { @@ -322,7 +293,7 @@ func (p *program) MainUpdate() { p.scrollPos++ } - p.scrollPos = clamp(p.scrollPos, 0, p.Lines.Len) + p.scrollPos = clamp(p.scrollPos, 0, p.Paras.Len) } // Delete inputs @@ -339,12 +310,17 @@ func (p *program) MainUpdate() { sepLinePos.SetY(2 * p.GlyphRend.Atlas.LineHeight) // Draw textBuf - linesIt := p.Lines.Iterator() - linesIt.GotoIndex(p.scrollPos) + parasIt := p.Paras.Iterator() + parasIt.GotoIndex(p.scrollPos) p.lastCmdCharPos.Data = gglm.NewVec3(0, float32(p.GlyphRend.ScreenHeight)-p.GlyphRend.Atlas.LineHeight, 0).Data - for v, done := linesIt.Next(); !done && p.lastCmdCharPos.Y() >= sepLinePos.Y(); v, done = linesIt.Next() { + for v, done := parasIt.Next(); !done && p.lastCmdCharPos.Y() >= sepLinePos.Y(); v, done = parasIt.Next() { - v1, v2 := p.textBuf.ViewsFromTo(v.StartIndex%uint64(p.textBuf.Cap), v.EndIndex%uint64(p.textBuf.Cap)) + if !p.IsParaValid(&v) { + p.scrollPos++ + continue + } + + v1, v2 := p.textBuf.ViewsFromToWriteCount(v.StartIndex, v.EndIndex) if len(v1) > 0 && v1[0] == '\n' { v1 = v1[1:] } @@ -359,32 +335,8 @@ func (p *program) MainUpdate() { p.lastCmdCharPos.Data = p.SyntaxHighlightAndDraw(p.cmdBuf[:p.cmdBufLen], *p.lastCmdCharPos).Data } -func bytesToRunes(b []byte) []rune { - - runeCount := utf8.RuneCount(b) - if runeCount == 0 { - return []rune{} - } - - // @PERF We should use a pre-allocated buffer here - out := make([]rune, 0, runeCount) - for { - - r, size := utf8.DecodeRune(b) - if r == utf8.RuneError { - break - } - - out = append(out, r) - b = b[size:] - } - - return out -} - func (p *program) DrawTextAnsiCodes(bs []byte, pos gglm.Vec3) gglm.Vec3 { - startPos := pos.Clone() currColor := p.Settings.DefaultColor draw := func(rs []rune) { @@ -397,7 +349,7 @@ func (p *program) DrawTextAnsiCodes(bs []byte, pos gglm.Vec3) gglm.Vec3 { // @PERF We could probably use bytes.IndexByte here if r == '\n' { pos.Data = p.GlyphRend.DrawTextOpenGLAbsRectWithStartPos(rs[startIndex:i], &pos, gglm.NewVec3(0, 0, 0), gglm.NewVec2(float32(p.GlyphRend.ScreenWidth), 2*p.GlyphRend.Atlas.LineHeight), &currColor).Data - pos.SetX(startPos.X()) + pos.SetX(0) pos.AddY(-p.GlyphRend.Atlas.LineHeight) startIndex = i + 1 continue @@ -666,7 +618,7 @@ func (p *program) HandleReturn() { }() } -func (p *program) ParseLines(bs []byte) { +func (p *program) ParseParas(bs []byte) { checkedBytes := uint64(0) for len(bs) > 0 { @@ -679,19 +631,19 @@ func (p *program) ParseLines(bs []byte) { bs = bs[index+1:] checkedBytes += uint64(index + 1) - p.CurrLine.EndIndex = p.textBuf.WrittenElements + checkedBytes - 1 - p.WriteLine(&p.CurrLine) - p.CurrLine.StartIndex = p.textBuf.WrittenElements + checkedBytes - 1 + p.CurrPara.EndIndex = p.textBuf.WrittenElements + checkedBytes + p.WritePara(&p.CurrPara) + p.CurrPara.StartIndex = p.textBuf.WrittenElements + checkedBytes } } -func (p *program) WriteLine(l *Line) { - assert.T(l.StartIndex <= l.EndIndex, "Invalid line: %+v\n", l) - p.Lines.Write(*l) +func (p *program) WritePara(para *Para) { + assert.T(para.StartIndex <= para.EndIndex, "Invalid line: %+v\n", para) + p.Paras.Write(*para) } -func (p *program) IsLineValid(l *Line) bool { - isValid := p.textBuf.WrittenElements-l.StartIndex <= uint64(p.textBuf.Cap) +func (p *program) IsParaValid(l *Para) bool { + isValid := p.textBuf.WrittenElements-l.StartIndex < uint64(p.textBuf.Cap) return isValid } @@ -845,6 +797,36 @@ func (p *program) HandleWindowResize() { p.CellCount = p.CellCountX * p.CellCountY } +func (p *program) WriteToTextBuf(text []byte) { + // This is locked because running cmds are potentially writing to it same time we are + p.textBufMutex.Lock() + + p.ParseParas(text) + p.textBuf.Write(text...) + + p.textBufMutex.Unlock() + + // @Todo we need better handling here + p.scrollPos = clamp(p.Paras.Len-p.CellCountY+3, 0, p.Paras.Len) +} + +func (p *program) WriteToCmdBuf(text []rune) { + + delta := int64(len(text)) + newHeadPos := p.cmdBufLen + delta + if newHeadPos <= defaultCmdBufSize { + + copy(p.cmdBuf[p.cursorCharIndex+delta:], p.cmdBuf[p.cursorCharIndex:]) + copy(p.cmdBuf[p.cursorCharIndex:], text) + + p.cursorCharIndex += delta + p.cmdBufLen = newHeadPos + return + } + + assert.T(false, "Circular buffer not implemented for cmd buf") +} + func FloorF32(x float32) float32 { return float32(math.Floor(float64(x))) } @@ -870,6 +852,29 @@ func clamp[T constraints.Ordered](x, min, max T) T { return x } +func bytesToRunes(b []byte) []rune { + + runeCount := utf8.RuneCount(b) + if runeCount == 0 { + return []rune{} + } + + // @PERF We should use a pre-allocated buffer here + out := make([]rune, 0, runeCount) + for { + + r, size := utf8.DecodeRune(b) + if r == utf8.RuneError { + break + } + + out = append(out, r) + b = b[size:] + } + + return out +} + func FindNthOrLastIndex[T comparable](arr []T, x T, startIndex, n int64) (lastIndex int64) { lastIndex = -1 diff --git a/ring/ring.go b/ring/ring.go index 60e70f9..7df29b2 100755 --- a/ring/ring.go +++ b/ring/ring.go @@ -1,6 +1,7 @@ package ring import ( + "github.com/bloeys/nterm/assert" "golang.org/x/exp/constraints" ) @@ -73,10 +74,46 @@ func (b *Buffer[T]) Get(index uint64) (val T) { return b.Data[(b.Start+int64(index))%b.Cap] } -func (b *Buffer[T]) AbsIndex(relIndex uint64) uint64 { +// AbsIndexFromRel takes an index relative to Buffer.Start and returns an absolute index into Buffer.Data +func (b *Buffer[T]) AbsIndexFromRel(relIndex uint64) uint64 { return uint64((b.Start + int64(relIndex)) % b.Cap) } +// RelIndexFromAbs takes an index into Buffer.Data and returns an index relative to Buffer.Start +func (b *Buffer[T]) RelIndexFromAbs(absIndex uint64) uint64 { + assert.T(absIndex < uint64(b.Cap), "absIndex must be between 0 and Buffer.Cap-1") + return uint64((int64(absIndex) - b.Start + b.Cap) % b.Cap) +} + +// AbsIndexFromWriteCount takes the total number of elements written and returns the index of the +// last written element after 'writeCount' writes. +// +// For example, if writeCount=1 then the index of last written element (the returned value) is zero. +// For a buffer of cap=4, after 5 writes the last updated index is absIndex=0 +// +// writeCount=0 is undefined because no elements have been written to yet. In this case zero is returned. +func (b *Buffer[T]) AbsIndexFromWriteCount(writeCount uint64) uint64 { + if writeCount == 0 { + return 0 + } + + return (writeCount - 1) % uint64(b.Cap) +} + +// RelIndexFromWriteCount takes the total number of elements written and returns the index of the +// last written element after 'writeCount' writes relative to the current Buffer.Start value. +// +// For example, if writeCount=1 then the index of last written element (the returned value) is zero. +// For a buffer of cap=4, after 5 writes the last updated index is absIndex=0 +// +// writeCount=0 is undefined because no elements have been written to yet. In this case zero is returned. +func (b *Buffer[T]) RelIndexFromWriteCount(writeCount uint64) uint64 { + if writeCount == 0 { + return 0 + } + return b.RelIndexFromAbs(b.AbsIndexFromWriteCount(writeCount)) +} + // Views returns two slices that have 'Len' elements in total between them. // The first slice is from Start till min(Start+Len, Cap). If Start+Len<=Cap then the first slice contains all the data and the second is empty. // If Start+Len>Cap then the first slice contains the data from Start till Cap, and the second slice contains data from 0 till Start+Len-Cap (basically the remaining elements to reach Len in total) @@ -91,11 +128,19 @@ func (b *Buffer[T]) Views() (v1, v2 []T) { } v1 = b.Data[b.Start:] - v2 = b.Data[:b.Start+b.Len-b.Cap] + v2 = b.Data[:(b.Start+b.Len)%b.Cap] return } -func (b *Buffer[T]) ViewsFromTo(fromIndex, toIndex uint64) (v1, v2 []T) { +func (b *Buffer[T]) ViewsFromToWriteCount(fromIndex, toIndex uint64) (v1, v2 []T) { + fromIndex = b.RelIndexFromWriteCount(fromIndex) + toIndex = b.RelIndexFromWriteCount(toIndex) + return b.ViewsFromToRelIndex(fromIndex, toIndex) +} + +// ViewsFromToRelIndex takes indices relative to Buffer.Start and returns views adjusted to contain +// elements between these two indices (inclusive) +func (b *Buffer[T]) ViewsFromToRelIndex(fromIndex, toIndex uint64) (v1, v2 []T) { toIndex++ // We convert the index into a length (e.g. from=0, to=0 is from=0, len=1) if toIndex <= fromIndex || fromIndex >= uint64(b.Len) { diff --git a/ring/ring_test.go b/ring/ring_test.go index 32c6a76..6d3fa07 100755 --- a/ring/ring_test.go +++ b/ring/ring_test.go @@ -71,77 +71,77 @@ func TestRing(t *testing.T) { // ViewsFromTo b2 = ring.NewBuffer[int](4) - v11, v22 := b2.ViewsFromTo(0, 0) + v11, v22 := b2.ViewsFromToRelIndex(0, 0) Check(t, 0, len(v11)) Check(t, 0, len(v22)) b2.Write(1, 2, 3, 4) - v11, v22 = b2.ViewsFromTo(5, 0) + v11, v22 = b2.ViewsFromToRelIndex(5, 0) Check(t, 0, len(v11)) Check(t, 0, len(v22)) - v11, v22 = b2.ViewsFromTo(0, 0) + v11, v22 = b2.ViewsFromToRelIndex(0, 0) Check(t, 1, len(v11)) Check(t, 0, len(v22)) CheckArr(t, []int{1}, v11) - v11, v22 = b2.ViewsFromTo(0, 1) + v11, v22 = b2.ViewsFromToRelIndex(0, 1) Check(t, 2, len(v11)) Check(t, 0, len(v22)) CheckArr(t, []int{1, 2}, v11) - v11, v22 = b2.ViewsFromTo(0, 3) + v11, v22 = b2.ViewsFromToRelIndex(0, 3) Check(t, 4, len(v11)) Check(t, 0, len(v22)) CheckArr(t, []int{1, 2, 3, 4}, v11) - v11, v22 = b2.ViewsFromTo(0, 4) + v11, v22 = b2.ViewsFromToRelIndex(0, 4) Check(t, 4, len(v11)) Check(t, 0, len(v22)) CheckArr(t, []int{1, 2, 3, 4}, v11) - v11, v22 = b2.ViewsFromTo(0, 40) + v11, v22 = b2.ViewsFromToRelIndex(0, 40) Check(t, 4, len(v11)) Check(t, 0, len(v22)) CheckArr(t, []int{1, 2, 3, 4}, v11) - v11, v22 = b2.ViewsFromTo(3, 40) + v11, v22 = b2.ViewsFromToRelIndex(3, 40) Check(t, 1, len(v11)) Check(t, 0, len(v22)) CheckArr(t, []int{4}, v11) b2.Write(5, 6) - v11, v22 = b2.ViewsFromTo(3, 40) + v11, v22 = b2.ViewsFromToRelIndex(3, 40) Check(t, 0, len(v11)) Check(t, 1, len(v22)) CheckArr(t, []int{6}, v22) - v11, v22 = b2.ViewsFromTo(1, 2) + v11, v22 = b2.ViewsFromToRelIndex(1, 2) Check(t, 1, len(v11)) Check(t, 1, len(v22)) CheckArr(t, []int{4}, v11) CheckArr(t, []int{5}, v22) - v11, v22 = b2.ViewsFromTo(0, 1) + v11, v22 = b2.ViewsFromToRelIndex(0, 1) Check(t, 2, len(v11)) Check(t, 0, len(v22)) CheckArr(t, []int{3, 4}, v11) - v11, v22 = b2.ViewsFromTo(0, 2) + v11, v22 = b2.ViewsFromToRelIndex(0, 2) Check(t, 2, len(v11)) Check(t, 1, len(v22)) CheckArr(t, []int{3, 4}, v11) CheckArr(t, []int{5}, v22) - v11, v22 = b2.ViewsFromTo(0, 3) + v11, v22 = b2.ViewsFromToRelIndex(0, 3) Check(t, 2, len(v11)) Check(t, 2, len(v22)) CheckArr(t, []int{3, 4}, v11) CheckArr(t, []int{5, 6}, v22) - v11, v22 = b2.ViewsFromTo(2, 3) + v11, v22 = b2.ViewsFromToRelIndex(2, 3) Check(t, 0, len(v11)) Check(t, 2, len(v22)) CheckArr(t, []int{5, 6}, v22)