/
filebuffer.go
123 lines (106 loc) · 3.18 KB
/
filebuffer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package main
/*
data structire to store a file line by line
organized as a Slice of pointers to blocks,
top level data structure has unlimited size,
data blocks it points to are fixed in size (lineblocksize)
This allows for fast access to certain lines and is somehow
space efficient.
it is a "insert once and read often but never update" format.
(c) Holger Berger 2018
*/
import (
"bytes"
"fmt"
)
const lineblocksize = 1024 // number of lines in one block
const initialblocks = 1024 // initial number of slots for lineblocks
// FileBuffer is the top level data structure to store a file, refering to LineBlocks
type FileBuffer struct {
name string
lineblocks []*LineBlock
appendin int
}
// LineBlock is the second level structure holding the file lines
type LineBlock struct {
lines [lineblocksize]string
len int // lines in this list
firstline, lastline int // lines in files
}
// NewFileBuffer returns a new FileBuffer and initilaizes with one lineblock
func NewFileBuffer(name string) *FileBuffer {
var (
filebuffer FileBuffer
initiallineblock LineBlock
)
// get space for <initialblocks> blocks, and allocate one block for <lineblocksize> lines
filebuffer.lineblocks = make([]*LineBlock, 0, initialblocks)
filebuffer.name = name
filebuffer.lineblocks = append(filebuffer.lineblocks, &initiallineblock)
filebuffer.appendin = 0
//fmt.Println(len(filebuffer.lineblocks))
return &filebuffer
}
// Addline adds a line to a FileBuffer, appending new LineBlocks if needed
func (f *FileBuffer) Addline(linenr int, line string) {
// we need a new block
if f.lineblocks[f.appendin].len >= lineblocksize {
var newlineblock LineBlock
f.lineblocks = append(f.lineblocks, &newlineblock)
f.appendin++
// some progress bar every 50K lines for large files
// guess nobody will ever notice :-)
if f.appendin%50 == 0 {
fmt.Print(".")
}
}
// insert line and increment counters
lb := &f.lineblocks[f.appendin]
if (*lb).len == 0 {
(*lb).firstline = linenr
}
(*lb).lines[(*lb).len] = expandtabs(line)
(*lb).lastline = linenr
(*lb).len++
}
// GetLine returns given Linenumber as string
func (f *FileBuffer) GetLine(linenr int) string {
block := (linenr - 1) / lineblocksize
lb := &f.lineblocks[block]
if (*lb).firstline > linenr || (*lb).lastline < linenr {
panic("Internal error!")
}
return (*lb).lines[linenr-(*lb).firstline]
}
// expandtabs replaces tab with spaces, we assume tabs of width 8 here
func expandtabsSlow(line string) string {
var result string
var spaces int
for pos := 0; pos < len(line); pos++ {
if line[pos] == '\t' {
spaces = (((pos / 8) + 1) * 8) - pos
for i := 0; i < spaces; i++ {
result += " "
}
} else {
result += string(line[pos])
}
}
return result
}
// expandtabs replaces tab with spaces, we assume tabs of width 8 here
func expandtabs(line string) string {
var result bytes.Buffer // this is 4x faster than string concatenation
var spaces int
for pos := 0; pos < len(line); pos++ {
if line[pos] == '\t' {
spaces = (((pos / 8) + 1) * 8) - pos
for i := 0; i < spaces; i++ {
result.WriteString(" ")
}
} else {
result.WriteRune(rune(line[pos]))
}
}
return result.String()
}