mirror of
https://github.com/bloeys/nterm.git
synced 2025-12-29 06:28:20 +00:00
Somewhat doing proper scrollback with wrapping
This commit is contained in:
210
main.go
210
main.go
@ -49,13 +49,14 @@ type Cmd struct {
|
|||||||
Stderr io.ReadCloser
|
Stderr io.ReadCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
// Para represents a paragraph, a line of text between two new lines
|
// Para represents a paragraph, a series of characters between two new-lines.
|
||||||
|
// The indices are in terms of total written elements to the ring buffer
|
||||||
type Para struct {
|
type Para struct {
|
||||||
StartIndex, EndIndex uint64
|
StartIndex_WriteCount, EndIndex_WriteCount uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Para) Size() uint64 {
|
func (l *Para) Size() uint64 {
|
||||||
size := l.EndIndex - l.StartIndex
|
size := l.EndIndex_WriteCount - l.StartIndex_WriteCount
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +105,7 @@ const (
|
|||||||
hinting = font.HintingNone
|
hinting = font.HintingNone
|
||||||
|
|
||||||
defaultCmdBufSize = 4 * 1024
|
defaultCmdBufSize = 4 * 1024
|
||||||
|
defaultParaBufSize = 10 * 1024
|
||||||
defaultTextBufSize = 4 * 1024 * 1024
|
defaultTextBufSize = 4 * 1024 * 1024
|
||||||
|
|
||||||
defaultScrollSpd = 1
|
defaultScrollSpd = 1
|
||||||
@ -142,7 +144,7 @@ func main() {
|
|||||||
imguiInfo: nmageimgui.NewImGUI(),
|
imguiInfo: nmageimgui.NewImGUI(),
|
||||||
FontSize: 40,
|
FontSize: 40,
|
||||||
|
|
||||||
Paras: ring.NewBuffer[Para](defaultTextBufSize),
|
Paras: ring.NewBuffer[Para](defaultParaBufSize),
|
||||||
|
|
||||||
textBuf: ring.NewBuffer[byte](defaultTextBufSize),
|
textBuf: ring.NewBuffer[byte](defaultTextBufSize),
|
||||||
|
|
||||||
@ -285,15 +287,20 @@ func (p *program) MainUpdate() {
|
|||||||
p.cursorCharIndex = p.cmdBufLen
|
p.cursorCharIndex = p.cmdBufLen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if input.KeyDown(sdl.K_LCTRL) && input.KeyClicked(sdl.K_END) {
|
||||||
|
p.scrollPos = p.textBuf.Len - 1
|
||||||
|
}
|
||||||
|
|
||||||
if mouseWheelYNorm := -int64(input.GetMouseWheelYNorm()); mouseWheelYNorm != 0 {
|
if mouseWheelYNorm := -int64(input.GetMouseWheelYNorm()); mouseWheelYNorm != 0 {
|
||||||
|
|
||||||
|
charsPerLine, _ := p.GridSize()
|
||||||
if mouseWheelYNorm < 0 {
|
if mouseWheelYNorm < 0 {
|
||||||
p.scrollPos--
|
p.scrollPos = FindNLinesIndexIterator(p.textBuf.Iterator(), p.Paras.Iterator(), p.scrollPos, -p.scrollSpd, charsPerLine-1)
|
||||||
} else {
|
} else {
|
||||||
p.scrollPos++
|
p.scrollPos = FindNLinesIndexIterator(p.textBuf.Iterator(), p.Paras.Iterator(), p.scrollPos, p.scrollSpd, charsPerLine-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.scrollPos = clamp(p.scrollPos, 0, p.Paras.Len)
|
p.scrollPos = clamp(p.scrollPos, 0, p.textBuf.Len-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete inputs
|
// Delete inputs
|
||||||
@ -310,24 +317,12 @@ func (p *program) MainUpdate() {
|
|||||||
sepLinePos.SetY(2 * p.GlyphRend.Atlas.LineHeight)
|
sepLinePos.SetY(2 * p.GlyphRend.Atlas.LineHeight)
|
||||||
|
|
||||||
// Draw textBuf
|
// Draw textBuf
|
||||||
parasIt := p.Paras.Iterator()
|
gw, gh := p.GridSize()
|
||||||
parasIt.GotoIndex(p.scrollPos)
|
v1, v2 := p.textBuf.ViewsFromToRelIndex(uint64(p.scrollPos), uint64(p.scrollPos)+uint64(gw*gh))
|
||||||
|
|
||||||
p.lastCmdCharPos.Data = gglm.NewVec3(0, float32(p.GlyphRend.ScreenHeight)-p.GlyphRend.Atlas.LineHeight, 0).Data
|
p.lastCmdCharPos.Data = gglm.NewVec3(0, float32(p.GlyphRend.ScreenHeight)-p.GlyphRend.Atlas.LineHeight, 0).Data
|
||||||
for v, done := parasIt.Next(); !done && p.lastCmdCharPos.Y() >= sepLinePos.Y(); v, done = parasIt.Next() {
|
p.lastCmdCharPos.Data = p.DrawTextAnsiCodes(v1, *p.lastCmdCharPos).Data
|
||||||
|
p.lastCmdCharPos.Data = p.DrawTextAnsiCodes(v2, *p.lastCmdCharPos).Data
|
||||||
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:]
|
|
||||||
}
|
|
||||||
|
|
||||||
p.lastCmdCharPos.Data = p.DrawTextAnsiCodes(v1, *p.lastCmdCharPos).Data
|
|
||||||
p.lastCmdCharPos.Data = p.DrawTextAnsiCodes(v2, *p.lastCmdCharPos).Data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw cmd buf
|
// Draw cmd buf
|
||||||
p.lastCmdCharPos.SetX(0)
|
p.lastCmdCharPos.SetX(0)
|
||||||
@ -496,7 +491,6 @@ func (p *program) HandleReturn() {
|
|||||||
p.cmdBufLen = 0
|
p.cmdBufLen = 0
|
||||||
p.cursorCharIndex = 0
|
p.cursorCharIndex = 0
|
||||||
|
|
||||||
// @PERF
|
|
||||||
cmdStr := string(cmdRunes)
|
cmdStr := string(cmdRunes)
|
||||||
cmdBytes := []byte(cmdStr)
|
cmdBytes := []byte(cmdStr)
|
||||||
p.WriteToTextBuf(cmdBytes)
|
p.WriteToTextBuf(cmdBytes)
|
||||||
@ -631,19 +625,19 @@ func (p *program) ParseParas(bs []byte) {
|
|||||||
bs = bs[index+1:]
|
bs = bs[index+1:]
|
||||||
|
|
||||||
checkedBytes += uint64(index + 1)
|
checkedBytes += uint64(index + 1)
|
||||||
p.CurrPara.EndIndex = p.textBuf.WrittenElements + checkedBytes
|
p.CurrPara.EndIndex_WriteCount = p.textBuf.WrittenElements + checkedBytes
|
||||||
p.WritePara(&p.CurrPara)
|
p.WritePara(&p.CurrPara)
|
||||||
p.CurrPara.StartIndex = p.textBuf.WrittenElements + checkedBytes
|
p.CurrPara.StartIndex_WriteCount = p.textBuf.WrittenElements + checkedBytes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *program) WritePara(para *Para) {
|
func (p *program) WritePara(para *Para) {
|
||||||
assert.T(para.StartIndex <= para.EndIndex, "Invalid line: %+v\n", para)
|
assert.T(para.StartIndex_WriteCount <= para.EndIndex_WriteCount, "Invalid line: %+v\n", para)
|
||||||
p.Paras.Write(*para)
|
p.Paras.Write(*para)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *program) IsParaValid(l *Para) bool {
|
func IsParaValid(textBuf *ring.Buffer[byte], l *Para) bool {
|
||||||
isValid := p.textBuf.WrittenElements-l.StartIndex < uint64(p.textBuf.Cap)
|
isValid := textBuf.WrittenElements-l.StartIndex_WriteCount < uint64(textBuf.Cap)
|
||||||
return isValid
|
return isValid
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -919,9 +913,7 @@ 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
|
// 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.
|
// but only a single rune. So in most cases to get '-n' lines backwards you should request '-n-1' lines.
|
||||||
func FindNLinesIndexIterator(it ring.Iterator[byte], startIndex, n, charsPerLine int64) (newIndex, newSize int64) {
|
func FindNLinesIndexIterator(it ring.Iterator[byte], paraIt ring.Iterator[Para], startIndex, n, charsPerLine int64) (newIndex int64) {
|
||||||
|
|
||||||
// If nothing changes (e.g. already at end of iterator) then we will stay at the same place
|
|
||||||
|
|
||||||
done := false
|
done := false
|
||||||
read := 0
|
read := 0
|
||||||
@ -944,9 +936,6 @@ func FindNLinesIndexIterator(it ring.Iterator[byte], startIndex, n, charsPerLine
|
|||||||
read, done = it.NextN(buf[bytesToKeep:], 4)
|
read, done = it.NextN(buf[bytesToKeep:], 4)
|
||||||
|
|
||||||
r, size := utf8.DecodeRune(buf[:bytesToKeep+read])
|
r, size := utf8.DecodeRune(buf[:bytesToKeep+read])
|
||||||
if r == utf8.RuneError {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
bytesToKeep += read - size
|
bytesToKeep += read - size
|
||||||
copy(buf, buf[size:size+bytesToKeep])
|
copy(buf, buf[size:size+bytesToKeep])
|
||||||
|
|
||||||
@ -957,7 +946,6 @@ func FindNLinesIndexIterator(it ring.Iterator[byte], startIndex, n, charsPerLine
|
|||||||
if charsSeenThisLine == charsPerLine || r == '\n' {
|
if charsSeenThisLine == charsPerLine || r == '\n' {
|
||||||
|
|
||||||
charsSeenThisLine = 0
|
charsSeenThisLine = 0
|
||||||
newSize = int64(size)
|
|
||||||
newIndex = startIndex + bytesSeen
|
newIndex = startIndex + bytesSeen
|
||||||
|
|
||||||
n--
|
n--
|
||||||
@ -969,6 +957,24 @@ func FindNLinesIndexIterator(it ring.Iterator[byte], startIndex, n, charsPerLine
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
extraIt := it.Buf.Iterator()
|
||||||
|
if it.Buf.Get(uint64(startIndex)) == '\n' && it.Buf.Get(uint64(startIndex-1)) == '\n' {
|
||||||
|
|
||||||
|
charsIntoLine := getWrappedLineLen(extraIt, paraIt, startIndex-1, charsPerLine)
|
||||||
|
if charsIntoLine > 0 {
|
||||||
|
n++
|
||||||
|
it.Prev()
|
||||||
|
it.Prev()
|
||||||
|
bytesSeen += 2
|
||||||
|
charsSeenThisLine = charsPerLine - int64(charsIntoLine)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
charsIntoLine := getWrappedLineLen(extraIt, paraIt, startIndex, charsPerLine)
|
||||||
|
if charsIntoLine > 0 {
|
||||||
|
charsSeenThisLine = charsPerLine - int64(charsIntoLine) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// @Todo this has wrong behavior when dealing with wrapped lines because we don't know what X position to be in
|
// @Todo this has wrong behavior when dealing with wrapped lines because we don't know what X position to be in
|
||||||
// after going up a line. Are we in the middle of the line? The only way to know is to draw from the start of this paragraph
|
// after going up a line. Are we in the middle of the line? The only way to know is to draw from the start of this paragraph
|
||||||
// till the start index, which will allows us to get accurate information on wrapping.
|
// till the start index, which will allows us to get accurate information on wrapping.
|
||||||
@ -979,9 +985,6 @@ func FindNLinesIndexIterator(it ring.Iterator[byte], startIndex, n, charsPerLine
|
|||||||
read, done = it.PrevN(buf[bytesToKeep:], 4)
|
read, done = it.PrevN(buf[bytesToKeep:], 4)
|
||||||
|
|
||||||
r, size := utf8.DecodeRune(buf[:bytesToKeep+read])
|
r, size := utf8.DecodeRune(buf[:bytesToKeep+read])
|
||||||
if r == utf8.RuneError {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
bytesToKeep += read - size
|
bytesToKeep += read - size
|
||||||
copy(buf, buf[size:size+bytesToKeep])
|
copy(buf, buf[size:size+bytesToKeep])
|
||||||
|
|
||||||
@ -991,23 +994,140 @@ func FindNLinesIndexIterator(it ring.Iterator[byte], startIndex, n, charsPerLine
|
|||||||
// If this is true we covered one line
|
// If this is true we covered one line
|
||||||
if charsSeenThisLine == charsPerLine || r == '\n' {
|
if charsSeenThisLine == charsPerLine || r == '\n' {
|
||||||
|
|
||||||
charsSeenThisLine = 0
|
newIndex = startIndex - bytesSeen + int64(size)
|
||||||
newSize = int64(size)
|
|
||||||
newIndex = startIndex - bytesSeen + newSize
|
|
||||||
|
|
||||||
n++
|
n++
|
||||||
if n >= 0 {
|
if n >= 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
charsSeenThisLine = 0
|
||||||
|
if r == '\n' {
|
||||||
|
|
||||||
|
// var charsIntoLine int64
|
||||||
|
// if it.Buf.Get(uint64(newIndex)) == '\n' && it.Buf.Get(uint64(newIndex-1)) == '\n' {
|
||||||
|
// charsIntoLine = getWrappedLineLen(extraIt, paraIt, newIndex-1, charsPerLine)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if charsIntoLine > 0 {
|
||||||
|
// charsSeenThisLine = charsPerLine - int64(charsIntoLine) - 1
|
||||||
|
// } else {
|
||||||
|
// charsSeenThisLine = 0
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we reached beginning of buffer before finding a new line then newIndex is zero
|
// If we reached beginning of buffer before finding a new line then newIndex is zero
|
||||||
if startIndex-bytesSeen == 0 {
|
if startIndex-bytesSeen == 0 {
|
||||||
newIndex = 0
|
newIndex = 0
|
||||||
newSize = 0
|
}
|
||||||
|
|
||||||
|
fmt.Println("Stopped at:", it.Buf.Get(uint64(newIndex)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return newIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
func getWrappedLineLen(it ring.Iterator[byte], paraIt ring.Iterator[Para], startIndexRel, charsPerLine int64) int64 {
|
||||||
|
|
||||||
|
done := false
|
||||||
|
read := 0
|
||||||
|
bytesToKeep := 0
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
bytesSeen := int64(0)
|
||||||
|
|
||||||
|
paraIt.GotoStart()
|
||||||
|
it.GotoIndex(startIndexRel)
|
||||||
|
|
||||||
|
// @PERF We need a faster way of doing this
|
||||||
|
// Find para that contains the start index
|
||||||
|
var para *Para
|
||||||
|
for p, done := paraIt.NextPtr(); !done; p, done = paraIt.NextPtr() {
|
||||||
|
|
||||||
|
if !IsParaValid(it.Buf, p) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
x := it.Buf.RelIndexFromWriteCount(p.EndIndex_WriteCount)
|
||||||
|
if startIndexRel > int64(x) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
para = p
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if para == nil {
|
||||||
|
|
||||||
|
paraIt.GotoEnd()
|
||||||
|
para, _ = paraIt.PrevPtr()
|
||||||
|
|
||||||
|
// If there are no paragraphs we just return the startIndex
|
||||||
|
if para == nil {
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return newIndex, newSize
|
// Find how far into the line the selected char is
|
||||||
|
charsIntoLine := int64(0)
|
||||||
|
if startIndexRel != int64(it.Buf.RelIndexFromWriteCount(para.StartIndex_WriteCount+1)) {
|
||||||
|
|
||||||
|
hasWrap := false
|
||||||
|
// Start at paragraph beginning till startIndex character
|
||||||
|
it.GotoIndex(int64(it.Buf.RelIndexFromWriteCount(para.StartIndex_WriteCount + 1)))
|
||||||
|
for int64(it.CurrToRelIndex()) < startIndexRel && !done {
|
||||||
|
|
||||||
|
read, done = it.NextN(buf[bytesToKeep:], 4)
|
||||||
|
|
||||||
|
r, size := utf8.DecodeRune(buf[:bytesToKeep+read])
|
||||||
|
bytesToKeep += read - size
|
||||||
|
copy(buf, buf[size:size+bytesToKeep])
|
||||||
|
|
||||||
|
charsIntoLine++
|
||||||
|
bytesSeen += int64(size)
|
||||||
|
|
||||||
|
if r == '\n' {
|
||||||
|
panic("bruh")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is true we covered one line
|
||||||
|
if charsIntoLine == charsPerLine {
|
||||||
|
hasWrap = true
|
||||||
|
charsIntoLine = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Previous loop potentially jumps 4 bytes a tick which means we can break out of it
|
||||||
|
// by moving 4 bytes and reaching startIndex, but then charsIntoLine doesn't process the remaining
|
||||||
|
// bytes to see how many bytes they make, so these left overs we cover here
|
||||||
|
it.GotoIndex(int64(it.Buf.RelIndexFromWriteCount(para.StartIndex_WriteCount + 1)))
|
||||||
|
paraStartIndexRel := int64(it.CurrToRelIndex())
|
||||||
|
for bytesToKeep > 0 && paraStartIndexRel+bytesSeen < startIndexRel {
|
||||||
|
|
||||||
|
r, size := utf8.DecodeRune(buf[:bytesToKeep])
|
||||||
|
bytesToKeep -= size
|
||||||
|
copy(buf, buf[size:size+bytesToKeep])
|
||||||
|
|
||||||
|
charsIntoLine++
|
||||||
|
bytesSeen += int64(size)
|
||||||
|
|
||||||
|
if r == '\n' {
|
||||||
|
panic("bruh")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is true we covered one line
|
||||||
|
if charsIntoLine == charsPerLine {
|
||||||
|
hasWrap = true
|
||||||
|
charsIntoLine = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasWrap {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return charsIntoLine
|
||||||
}
|
}
|
||||||
|
|||||||
19
ring/ring.go
19
ring/ring.go
@ -200,8 +200,9 @@ func NewBuffer[T any](capacity uint64) *Buffer[T] {
|
|||||||
//
|
//
|
||||||
// Indices used are all relative to 'Buffer.Start'
|
// Indices used are all relative to 'Buffer.Start'
|
||||||
type Iterator[T any] struct {
|
type Iterator[T any] struct {
|
||||||
V1 []T
|
Buf *Buffer[T]
|
||||||
V2 []T
|
V1 []T
|
||||||
|
V2 []T
|
||||||
|
|
||||||
// Curr is the index of the element that will be returned on Next(),
|
// Curr is the index of the element that will be returned on Next(),
|
||||||
// which means it is an index into V1 or V2 and so is relative to Buffer.Start value at the time
|
// which means it is an index into V1 or V2 and so is relative to Buffer.Start value at the time
|
||||||
@ -214,6 +215,15 @@ func (it *Iterator[T]) Len() int64 {
|
|||||||
return int64(len(it.V1) + len(it.V2))
|
return int64(len(it.V1) + len(it.V2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (it *Iterator[T]) CurrToRelIndex() uint64 {
|
||||||
|
|
||||||
|
if it.InV1 {
|
||||||
|
return uint64(it.Curr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint64(it.Curr) + uint64(len(it.V1))
|
||||||
|
}
|
||||||
|
|
||||||
func (it *Iterator[T]) NextPtr() (v *T, done bool) {
|
func (it *Iterator[T]) NextPtr() (v *T, done bool) {
|
||||||
|
|
||||||
if it.InV1 {
|
if it.InV1 {
|
||||||
@ -365,7 +375,9 @@ func (it *Iterator[T]) GotoStart() {
|
|||||||
it.InV1 = len(it.V1) > 0
|
it.InV1 = len(it.V1) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GotoIndex goes to the index n relative to Buffer.Start
|
// GotoIndex goes to the index n relative to Buffer.Start.
|
||||||
|
// If n<=0 this is equivalent to Iterator.GotoStart()
|
||||||
|
// if n>=Buffer.Len this is equivalent to Iterator.GotoEnd()
|
||||||
func (it *Iterator[T]) GotoIndex(n int64) {
|
func (it *Iterator[T]) GotoIndex(n int64) {
|
||||||
|
|
||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
@ -400,6 +412,7 @@ func (it *Iterator[T]) GotoEnd() {
|
|||||||
func NewIterator[T any](b *Buffer[T]) Iterator[T] {
|
func NewIterator[T any](b *Buffer[T]) Iterator[T] {
|
||||||
v1, v2 := b.Views()
|
v1, v2 := b.Views()
|
||||||
return Iterator[T]{
|
return Iterator[T]{
|
||||||
|
Buf: b,
|
||||||
V1: v1,
|
V1: v1,
|
||||||
V2: v2,
|
V2: v2,
|
||||||
Curr: 0,
|
Curr: 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user