1
0
Эх сурвалжийг харах

Use Lineindex system, so that only visible parts of the Rope is rendered

Johan Rong 1 сар өмнө
parent
commit
cf1c563912

+ 64 - 52
internal/buffer/buffer.go

@@ -17,14 +17,17 @@ func (bm *BufferManager) Current() *Buffer {
 }
 
 type Buffer struct {
-	Rope *rope.Node
-	CM   CursorManager
-	Path string
+	Rope    *rope.Node
+	LI      *LineIndex
+	CM      CursorManager
+	Path    string
+	TopLine int
 }
 
 func NewBuffer() Buffer {
 	return Buffer{
 		Rope: rope.New([]byte{}),
+		LI:   NewLineIndex(),
 		CM: CursorManager{
 			Cursors:    []Cursor{{Offset: 0, Goal: 0}},
 			PrimaryIdx: 0,
@@ -36,6 +39,9 @@ func (buf *Buffer) ensureRope() {
 	if buf.Rope == nil {
 		buf.Rope = rope.New([]byte{})
 	}
+	if buf.LI == nil {
+		buf.LI = NewLineIndexFromRope(buf.Rope)
+	}
 }
 
 func (buf *Buffer) Insert(content rune) {
@@ -59,8 +65,10 @@ func (buf *Buffer) Insert(content rune) {
 		}
 
 		buf.Rope.Insert(pos, data)
+		buf.LI.InsertAt(pos, data)
+
 		cur.Offset = pos + shift
-		_, goal := LineCol(buf.Rope, cur.Offset)
+		_, goal := LineCol(buf, cur.Offset)
 		cur.Goal = goal
 
 		delta += shift
@@ -106,12 +114,13 @@ func (buf *Buffer) Delete() {
 		}
 
 		buf.Rope.Remove(start, pos)
+		buf.LI.RemoveAt(start, pos)
 
 		deleted := pos - start
 		delta -= deleted
 
 		cur.Offset = start
-		_, goal := LineCol(buf.Rope, cur.Offset)
+		_, goal := LineCol(buf, cur.Offset)
 		cur.Goal = goal
 	}
 
@@ -119,6 +128,8 @@ func (buf *Buffer) Delete() {
 }
 
 func (buf *Buffer) Clear() {
+	buf.ensureRope()
+
 	primaryCursor := &Cursor{
 		Offset: 0,
 		Goal:   0,
@@ -129,6 +140,7 @@ func (buf *Buffer) Clear() {
 	buf.CM.PrimaryIdx = 0
 
 	buf.Rope.Remove(0, buf.Rope.Len())
+	buf.LI = NewLineIndex()
 }
 
 func (buf *Buffer) MoveHoriz(dir int) {
@@ -142,7 +154,7 @@ func (buf *Buffer) MoveHoriz(dir int) {
 		case dir > 0:
 			cur.Offset = nextRuneEnd(buf.Rope, cur.Offset)
 		}
-		_, goal := LineCol(buf.Rope, cur.Offset)
+		_, goal := LineCol(buf, cur.Offset)
 		cur.Goal = goal
 	}
 
@@ -154,15 +166,15 @@ func (buf *Buffer) MoveVert(dir int) {
 
 	for i := range buf.CM.Cursors {
 		cur := &buf.CM.Cursors[i]
-		line, _ := LineCol(buf.Rope, cur.Offset)
+		line, _ := LineCol(buf, cur.Offset)
 
 		targetLine := line + dir
-		if targetLine < 0 || targetLine >= LineCount(buf.Rope) {
+		if targetLine < 0 || targetLine >= LineCount(buf) {
 			continue
 		}
 
-		lineStart := OffsetForLine(buf.Rope, targetLine)
-		lineEnd := lineContentEnd(buf.Rope, targetLine)
+		lineStart := OffsetForLine(buf, targetLine)
+		lineEnd := lineContentEnd(buf, targetLine)
 		lineLen := runeCount(buf.Rope, lineStart, lineEnd)
 
 		goal := cur.Goal
@@ -170,7 +182,7 @@ func (buf *Buffer) MoveVert(dir int) {
 			goal = lineLen
 		}
 
-		cur.Offset = OffsetForLineCol(buf.Rope, targetLine, goal)
+		cur.Offset = OffsetForLineCol(buf, targetLine, goal)
 	}
 
 	buf.CM.DeduplicateAndSort()
@@ -184,15 +196,15 @@ func (buf *Buffer) AddCursorVert(dir int) {
 	for i := range buf.CM.Cursors {
 		cur := &buf.CM.Cursors[i]
 
-		line, _ := LineCol(buf.Rope, cur.Offset)
+		line, _ := LineCol(buf, cur.Offset)
 
 		targetLine := line + dir
-		if targetLine < 0 || targetLine >= LineCount(buf.Rope) {
+		if targetLine < 0 || targetLine >= LineCount(buf) {
 			continue
 		}
 
-		lineStart := OffsetForLine(buf.Rope, targetLine)
-		lineEnd := lineContentEnd(buf.Rope, targetLine)
+		lineStart := OffsetForLine(buf, targetLine)
+		lineEnd := lineContentEnd(buf, targetLine)
 		lineLen := runeCount(buf.Rope, lineStart, lineEnd)
 
 		goal := cur.Goal
@@ -201,7 +213,7 @@ func (buf *Buffer) AddCursorVert(dir int) {
 		}
 
 		newCursors = append(newCursors, Cursor{
-			Offset: OffsetForLineCol(buf.Rope, targetLine, goal),
+			Offset: OffsetForLineCol(buf, targetLine, goal),
 			Goal:   goal,
 		})
 	}
@@ -211,6 +223,20 @@ func (buf *Buffer) AddCursorVert(dir int) {
 	buf.CM.DeduplicateAndSort()
 }
 
+func (buf *Buffer) ScrollToShow(line, maxHeight int) {
+	if maxHeight <= 0 {
+		return
+	}
+	if line < buf.TopLine {
+		buf.TopLine = line
+	} else if line >= buf.TopLine+maxHeight {
+		buf.TopLine = line - maxHeight + 1
+	}
+	if buf.TopLine < 0 {
+		buf.TopLine = 0
+	}
+}
+
 func (buf *Buffer) ClearCursors() {
 	primaryCursor := &Cursor{
 		Offset: buf.CM.Cursors[buf.CM.PrimaryIdx].Offset,
@@ -222,63 +248,49 @@ func (buf *Buffer) ClearCursors() {
 	buf.CM.PrimaryIdx = 0
 }
 
-func LineCount(r *rope.Node) int {
-	return r.Count(0, r.Len(), []byte{'\n'}) + 1
+func LineCount(buf *Buffer) int {
+	return buf.LI.Count()
 }
 
-func LineCol(r *rope.Node, offset int) (line, col int) {
-	offset = normalizeOffset(r, offset)
+func LineCol(buf *Buffer, offset int) (line, col int) {
+	offset = normalizeOffset(buf.Rope, offset)
 
 	if offset < 0 {
 		offset = 0
 	}
-	if offset > r.Len() {
-		offset = r.Len()
+	if offset > buf.Rope.Len() {
+		offset = buf.Rope.Len()
 	}
 
-	line = r.Count(0, offset, []byte{'\n'})
-
-	lineStart := 0
-	for i := offset - 1; i >= 0; i-- {
-		if r.At(i) == '\n' {
-			lineStart = i + 1
-			break
-		}
-	}
+	line = buf.LI.LineForOffset(offset)
+	lineStart := buf.LI.OffsetForLine(line)
 
-	col = runeCount(r, lineStart, offset)
+	col = runeCount(buf.Rope, lineStart, offset)
 	return
 }
 
-func OffsetForLine(r *rope.Node, targetLine int) int {
+func OffsetForLine(buf *Buffer, targetLine int) int {
 	if targetLine <= 0 {
 		return 0
 	}
-
-	line := 0
-	for i := 0; i < r.Len(); i++ {
-		if r.At(i) == '\n' {
-			line++
-			if line == targetLine {
-				return i + 1
-			}
-		}
+	if targetLine >= buf.LI.Count() {
+		return buf.Rope.Len()
 	}
 
-	return r.Len()
+	return buf.LI.OffsetForLine(targetLine)
 }
 
-func OffsetForLineCol(r *rope.Node, line int, col int) int {
+func OffsetForLineCol(buf *Buffer, line int, col int) int {
 	if col <= 0 {
-		return OffsetForLine(r, line)
+		return OffsetForLine(buf, line)
 	}
 
-	start := OffsetForLine(r, line)
-	end := lineContentEnd(r, line)
+	start := OffsetForLine(buf, line)
+	end := lineContentEnd(buf, line)
 
 	i := start
 	for n := 0; i < end && n < col; n++ {
-		_, size := utf8.DecodeRune(r.Slice(i, end))
+		_, size := utf8.DecodeRune(buf.Rope.Slice(i, end))
 		if size <= 0 {
 			size = 1
 		}
@@ -292,9 +304,9 @@ func OffsetForLineCol(r *rope.Node, line int, col int) int {
 	return i
 }
 
-func lineContentEnd(r *rope.Node, line int) int {
-	nextStart := OffsetForLine(r, line+1)
-	if nextStart > 0 && nextStart <= r.Len() && r.At(nextStart-1) == '\n' {
+func lineContentEnd(buf *Buffer, line int) int {
+	nextStart := OffsetForLine(buf, line+1)
+	if nextStart > 0 && nextStart <= buf.Rope.Len() && buf.Rope.At(nextStart-1) == '\n' {
 		return nextStart - 1
 	}
 	return nextStart
@@ -374,4 +386,4 @@ func (buf Buffer) String() string {
 	}
 
 	return string(buf.Rope.Value())
-}
+}

+ 108 - 0
internal/buffer/lineindex.go

@@ -0,0 +1,108 @@
+package buffer
+
+import (
+	"sort"
+
+	"github.com/zyedidia/rope"
+)
+
+type LineIndex struct {
+	starts []int
+}
+
+func NewLineIndex() *LineIndex {
+	return &LineIndex{starts: []int{0}}
+}
+
+func NewLineIndexFromRope(r *rope.Node) *LineIndex {
+	li := NewLineIndex()
+	li.Rebuild(r)
+	return li
+}
+
+func (li *LineIndex) Rebuild(r *rope.Node) {
+	starts := []int{0}
+	if r != nil {
+		data := r.Value()
+		for i, b := range data {
+			if b == '\n' {
+				starts = append(starts, i+1)
+			}
+		}
+	}
+	li.starts = starts
+}
+
+func (li *LineIndex) Count() int {
+	return len(li.starts)
+}
+
+func (li *LineIndex) LineForOffset(offset int) int {
+	// first index whose start is > offset; the line we want is one before that.
+	i := sort.Search(len(li.starts), func(i int) bool {
+		return li.starts[i] > offset
+	})
+	if i == 0 {
+		return 0
+	}
+	return i - 1
+}
+
+func (li *LineIndex) OffsetForLine(line int) int {
+	if line < 0 {
+		return li.starts[0]
+	}
+	if line >= len(li.starts) {
+		return li.starts[len(li.starts)-1]
+	}
+	return li.starts[line]
+}
+
+func (li *LineIndex) InsertAt(pos int, data []byte) {
+	shift := len(data)
+	if shift == 0 {
+		return
+	}
+
+	lowIdx := sort.Search(len(li.starts), func(i int) bool {
+		return li.starts[i] > pos
+	})
+
+	var newStarts []int
+	for j := 0; j < len(data); j++ {
+		if data[j] == '\n' {
+			newStarts = append(newStarts, pos+j+1)
+		}
+	}
+
+	result := make([]int, 0, len(li.starts)+len(newStarts))
+	result = append(result, li.starts[:lowIdx]...)
+	result = append(result, newStarts...)
+	for _, o := range li.starts[lowIdx:] {
+		result = append(result, o+shift)
+	}
+
+	li.starts = result
+}
+
+func (li *LineIndex) RemoveAt(start, end int) {
+	if end <= start {
+		return
+	}
+	removed := end - start
+
+	lowIdx := sort.Search(len(li.starts), func(i int) bool {
+		return li.starts[i] > start
+	})
+	highIdx := sort.Search(len(li.starts), func(i int) bool {
+		return li.starts[i] > end
+	})
+
+	result := make([]int, 0, lowIdx+(len(li.starts)-highIdx))
+	result = append(result, li.starts[:lowIdx]...)
+	for _, o := range li.starts[highIdx:] {
+		result = append(result, o-removed)
+	}
+
+	li.starts = result
+}

+ 1 - 1
internal/editor/model.go

@@ -20,7 +20,7 @@ func NewModel(screen tcell.Screen, style tcell.Style) Model {
 		Style:		   style,
 		Mode:		   ModeNormal,
 		BM:            buffer.BufferManager{
-			Buffers:       []buffer.Buffer{buffer.NewBuffer()},
+			Buffers:       []buffer.Buffer{buffer.NewBuffer(), buffer.NewBuffer()},
 			CurrentIdx:    0,
 			PaletteBuffer: buffer.NewBuffer(),
 		},

+ 21 - 7
internal/editor/render.go

@@ -14,8 +14,21 @@ func (m Model) Render() {
 	bLen := len(m.BM.Buffers)
 	bufWidth := sWidth / bLen
 
-	for i, buf := range m.BM.Buffers {
-		table := strings.SplitAfter(buf.String(), "\n")
+	for i := range m.BM.Buffers {
+		buf := &m.BM.Buffers[i]
+
+		primary := buf.CM.Cursors[buf.CM.PrimaryIdx]
+		curLine, _ := buffer.LineCol(buf, primary.Offset)
+		buf.ScrollToShow(curLine, maxHeight)
+
+		startOffset := buffer.OffsetForLine(buf, buf.TopLine)
+		endOffset := buf.Rope.Len()
+		if endLine := buf.TopLine + maxHeight; endLine < buffer.LineCount(buf) {
+			endOffset = buffer.OffsetForLine(buf, endLine)
+		}
+
+		visible := string(buf.Rope.Slice(startOffset, endOffset))
+		table := strings.SplitAfter(visible, "\n")
 		for j, line := range table {
 			if j + 1 > maxHeight { break }
 
@@ -29,14 +42,15 @@ func (m Model) Render() {
 
 		if i == m.BM.CurrentIdx || m.Mode == ModePalette {
 			for _, cur := range buf.CM.Cursors {
-				line, col := buffer.LineCol(buf.Rope, cur.Offset)
-				if line + 1 > maxHeight { continue }
+				line, col := buffer.LineCol(buf, cur.Offset)
+				screenLine := line - buf.TopLine
+				if screenLine < 0 || screenLine + 1 > maxHeight { continue }
 				if col + 1 > bufWidth { continue }
 
 				if m.Mode == ModeWrite {
-					m.Screen.SetContent(bufWidth * i + col, line, ' ', nil, m.Style.Reverse(true))				
+					m.Screen.SetContent(bufWidth * i + col, screenLine, ' ', nil, m.Style.Reverse(true))				
 				} else {
-					m.Screen.SetContent(bufWidth * i + col, line, ' ', nil, m.Style.Reverse(true).Foreground(color.Gray))	
+					m.Screen.SetContent(bufWidth * i + col, screenLine, ' ', nil, m.Style.Reverse(true).Foreground(color.Gray))	
 				}
 			}
 		}
@@ -63,7 +77,7 @@ func (m Model) Render() {
 		m.Screen.PutStrStyled(0, maxHeight + 1, m.BM.PaletteBuffer.String(), m.Style)
 
 		for _, cur := range m.BM.PaletteBuffer.CM.Cursors {
-			line, col := buffer.LineCol(m.BM.PaletteBuffer.Rope, cur.Offset)
+			line, col := buffer.LineCol(&m.BM.PaletteBuffer, cur.Offset)
 			if line + maxHeight != maxHeight { continue }
 			if col + 1 > sWidth { continue }