package main
import (
- "bytes"
+ "bufio"
+ "io"
"unicode/utf8"
+
+ "github.com/zyedidia/micro/cmd/micro/highlight"
)
func runeToByteIndex(n int, txt []byte) int {
return count
}
+// A Line contains the data in bytes as well as a highlight state, match
+// and a flag for whether the highlighting needs to be updated
+type Line struct {
+ data []byte
+
+ state highlight.State
+ match highlight.LineMatch
+ rehighlight bool
+}
+
// A LineArray simply stores and array of lines and makes it easy to insert
// and delete in it
type LineArray struct {
- lines [][]byte
+ lines []Line
+}
+
+// Append efficiently appends lines together
+// It allocates an additional 10000 lines if the original estimate
+// is incorrect
+func Append(slice []Line, data ...Line) []Line {
+ l := len(slice)
+ if l+len(data) > cap(slice) { // reallocate
+ newSlice := make([]Line, (l+len(data))+10000)
+ // The copy function is predeclared and works for any slice type.
+ copy(newSlice, slice)
+ slice = newSlice
+ }
+ slice = slice[0 : l+len(data)]
+ for i, c := range data {
+ slice[l+i] = c
+ }
+ return slice
}
// NewLineArray returns a new line array from an array of bytes
-func NewLineArray(text []byte) *LineArray {
+func NewLineArray(size int64, reader io.Reader) *LineArray {
la := new(LineArray)
- // Split the bytes into lines
- split := bytes.Split(text, []byte("\n"))
- la.lines = make([][]byte, len(split))
- for i := range split {
- la.lines[i] = make([]byte, len(split[i]))
- copy(la.lines[i], split[i])
+
+ la.lines = make([]Line, 0, 1000)
+
+ br := bufio.NewReader(reader)
+ var loaded int
+
+ n := 0
+ for {
+ data, err := br.ReadBytes('\n')
+ if len(data) > 1 && data[len(data)-2] == '\r' {
+ data = append(data[:len(data)-2], '\n')
+ if fileformat == 0 {
+ fileformat = 2
+ }
+ } else if len(data) > 0 {
+ if fileformat == 0 {
+ fileformat = 1
+ }
+ }
+
+ if n >= 1000 && loaded >= 0 {
+ totalLinesNum := int(float64(size) * (float64(n) / float64(loaded)))
+ newSlice := make([]Line, len(la.lines), totalLinesNum+10000)
+ // The copy function is predeclared and works for any slice type.
+ copy(newSlice, la.lines)
+ la.lines = newSlice
+ loaded = -1
+ }
+
+ if loaded >= 0 {
+ loaded += len(data)
+ }
+
+ if err != nil {
+ if err == io.EOF {
+ la.lines = Append(la.lines, Line{data[:], nil, nil, false})
+ // la.lines = Append(la.lines, Line{data[:len(data)]})
+ }
+ // Last line was read
+ break
+ } else {
+ // la.lines = Append(la.lines, Line{data[:len(data)-1]})
+ la.lines = Append(la.lines, Line{data[:len(data)-1], nil, nil, false})
+ }
+ n++
}
return la
// Returns the String representation of the LineArray
func (la *LineArray) String() string {
- return string(bytes.Join(la.lines, []byte("\n")))
+ str := ""
+ for i, l := range la.lines {
+ str += string(l.data)
+ if i != len(la.lines)-1 {
+ str += "\n"
+ }
+ }
+ return str
+}
+
+// SaveString returns the string that should be written to disk when
+// the line array is saved
+// It is the same as string but uses crlf or lf line endings depending
+func (la *LineArray) SaveString(useCrlf bool) string {
+ str := ""
+ for i, l := range la.lines {
+ str += string(l.data)
+ if i != len(la.lines)-1 {
+ if useCrlf {
+ str += "\r"
+ }
+ str += "\n"
+ }
+ }
+ return str
}
// NewlineBelow adds a newline below the given line number
func (la *LineArray) NewlineBelow(y int) {
- la.lines = append(la.lines, []byte(" "))
+ la.lines = append(la.lines, Line{[]byte(" "), nil, nil, false})
copy(la.lines[y+2:], la.lines[y+1:])
- la.lines[y+1] = []byte("")
+ la.lines[y+1] = Line{[]byte(""), la.lines[y].state, nil, false}
}
// inserts a byte array at a given location
func (la *LineArray) insert(pos Loc, value []byte) {
- x, y := runeToByteIndex(pos.X, la.lines[pos.Y]), pos.Y
+ x, y := runeToByteIndex(pos.X, la.lines[pos.Y].data), pos.Y
// x, y := pos.x, pos.y
for i := 0; i < len(value); i++ {
if value[i] == '\n' {
// inserts a byte at a given location
func (la *LineArray) insertByte(pos Loc, value byte) {
- la.lines[pos.Y] = append(la.lines[pos.Y], 0)
- copy(la.lines[pos.Y][pos.X+1:], la.lines[pos.Y][pos.X:])
- la.lines[pos.Y][pos.X] = value
+ la.lines[pos.Y].data = append(la.lines[pos.Y].data, 0)
+ copy(la.lines[pos.Y].data[pos.X+1:], la.lines[pos.Y].data[pos.X:])
+ la.lines[pos.Y].data[pos.X] = value
}
// JoinLines joins the two lines a and b
func (la *LineArray) JoinLines(a, b int) {
- la.insert(Loc{len(la.lines[a]), a}, la.lines[b])
+ la.insert(Loc{len(la.lines[a].data), a}, la.lines[b].data)
la.DeleteLine(b)
}
// Split splits a line at a given position
func (la *LineArray) Split(pos Loc) {
la.NewlineBelow(pos.Y)
- la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y][pos.X:])
+ la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y].data[pos.X:])
+ la.lines[pos.Y+1].state = la.lines[pos.Y].state
+ la.lines[pos.Y].state = nil
+ la.lines[pos.Y].match = nil
+ la.lines[pos.Y+1].match = nil
+ la.lines[pos.Y].rehighlight = true
la.DeleteToEnd(Loc{pos.X, pos.Y})
}
// removes from start to end
func (la *LineArray) remove(start, end Loc) string {
sub := la.Substr(start, end)
- startX := runeToByteIndex(start.X, la.lines[start.Y])
- endX := runeToByteIndex(end.X, la.lines[end.Y])
+ startX := runeToByteIndex(start.X, la.lines[start.Y].data)
+ endX := runeToByteIndex(end.X, la.lines[end.Y].data)
if start.Y == end.Y {
- la.lines[start.Y] = append(la.lines[start.Y][:startX], la.lines[start.Y][endX:]...)
+ la.lines[start.Y].data = append(la.lines[start.Y].data[:startX], la.lines[start.Y].data[endX:]...)
} else {
for i := start.Y + 1; i <= end.Y-1; i++ {
la.DeleteLine(start.Y + 1)
// DeleteToEnd deletes from the end of a line to the position
func (la *LineArray) DeleteToEnd(pos Loc) {
- la.lines[pos.Y] = la.lines[pos.Y][:pos.X]
+ la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X]
}
// DeleteFromStart deletes from the start of a line to the position
func (la *LineArray) DeleteFromStart(pos Loc) {
- la.lines[pos.Y] = la.lines[pos.Y][pos.X+1:]
+ la.lines[pos.Y].data = la.lines[pos.Y].data[pos.X+1:]
}
// DeleteLine deletes the line number
// DeleteByte deletes the byte at a position
func (la *LineArray) DeleteByte(pos Loc) {
- la.lines[pos.Y] = la.lines[pos.Y][:pos.X+copy(la.lines[pos.Y][pos.X:], la.lines[pos.Y][pos.X+1:])]
+ la.lines[pos.Y].data = la.lines[pos.Y].data[:pos.X+copy(la.lines[pos.Y].data[pos.X:], la.lines[pos.Y].data[pos.X+1:])]
}
// Substr returns the string representation between two locations
func (la *LineArray) Substr(start, end Loc) string {
- startX := runeToByteIndex(start.X, la.lines[start.Y])
- endX := runeToByteIndex(end.X, la.lines[end.Y])
+ startX := runeToByteIndex(start.X, la.lines[start.Y].data)
+ endX := runeToByteIndex(end.X, la.lines[end.Y].data)
if start.Y == end.Y {
- return string(la.lines[start.Y][startX:endX])
+ return string(la.lines[start.Y].data[startX:endX])
}
var str string
- str += string(la.lines[start.Y][startX:]) + "\n"
+ str += string(la.lines[start.Y].data[startX:]) + "\n"
for i := start.Y + 1; i <= end.Y-1; i++ {
- str += string(la.lines[i]) + "\n"
+ str += string(la.lines[i].data) + "\n"
}
- str += string(la.lines[end.Y][:endX])
+ str += string(la.lines[end.Y].data[:endX])
return str
}
+
+// State gets the highlight state for the given line number
+func (la *LineArray) State(lineN int) highlight.State {
+ return la.lines[lineN].state
+}
+
+// SetState sets the highlight state at the given line number
+func (la *LineArray) SetState(lineN int, s highlight.State) {
+ la.lines[lineN].state = s
+}
+
+// SetMatch sets the match at the given line number
+func (la *LineArray) SetMatch(lineN int, m highlight.LineMatch) {
+ la.lines[lineN].match = m
+}
+
+// Match retrieves the match for the given line number
+func (la *LineArray) Match(lineN int) highlight.LineMatch {
+ return la.lines[lineN].match
+}