]> git.lizzy.rs Git - micro.git/blob - cmd/micro/lineArray.go
Fix: mouse clicking with softwrap
[micro.git] / cmd / micro / lineArray.go
1 package main
2
3 import (
4         "bufio"
5         "bytes"
6         "io"
7         "unicode/utf8"
8 )
9
10 func runeToByteIndex(n int, txt []byte) int {
11         if n == 0 {
12                 return 0
13         }
14
15         count := 0
16         i := 0
17         for len(txt) > 0 {
18                 _, size := utf8.DecodeRune(txt)
19
20                 txt = txt[size:]
21                 count += size
22                 i++
23
24                 if i == n {
25                         break
26                 }
27         }
28         return count
29 }
30
31 // A LineArray simply stores and array of lines and makes it easy to insert
32 // and delete in it
33 type LineArray struct {
34         lines [][]byte
35 }
36
37 // NewLineArray returns a new line array from an array of bytes
38 func NewLineArray(reader io.Reader) *LineArray {
39         la := new(LineArray)
40         br := bufio.NewReader(reader)
41
42         i := 0
43         for {
44                 data, err := br.ReadBytes('\n')
45                 if err != nil {
46                         if err == io.EOF {
47                                 la.lines = append(la.lines, data[:len(data)])
48                         }
49                         // Last line was read
50                         break
51                 } else {
52                         la.lines = append(la.lines, data[:len(data)-1])
53                 }
54                 i++
55         }
56
57         return la
58 }
59
60 // Returns the String representation of the LineArray
61 func (la *LineArray) String() string {
62         return string(bytes.Join(la.lines, []byte("\n")))
63 }
64
65 // NewlineBelow adds a newline below the given line number
66 func (la *LineArray) NewlineBelow(y int) {
67         la.lines = append(la.lines, []byte(" "))
68         copy(la.lines[y+2:], la.lines[y+1:])
69         la.lines[y+1] = []byte("")
70 }
71
72 // inserts a byte array at a given location
73 func (la *LineArray) insert(pos Loc, value []byte) {
74         x, y := runeToByteIndex(pos.X, la.lines[pos.Y]), pos.Y
75         // x, y := pos.x, pos.y
76         for i := 0; i < len(value); i++ {
77                 if value[i] == '\n' {
78                         la.Split(Loc{x, y})
79                         x = 0
80                         y++
81                         continue
82                 }
83                 la.insertByte(Loc{x, y}, value[i])
84                 x++
85         }
86 }
87
88 // inserts a byte at a given location
89 func (la *LineArray) insertByte(pos Loc, value byte) {
90         la.lines[pos.Y] = append(la.lines[pos.Y], 0)
91         copy(la.lines[pos.Y][pos.X+1:], la.lines[pos.Y][pos.X:])
92         la.lines[pos.Y][pos.X] = value
93 }
94
95 // JoinLines joins the two lines a and b
96 func (la *LineArray) JoinLines(a, b int) {
97         la.insert(Loc{len(la.lines[a]), a}, la.lines[b])
98         la.DeleteLine(b)
99 }
100
101 // Split splits a line at a given position
102 func (la *LineArray) Split(pos Loc) {
103         la.NewlineBelow(pos.Y)
104         la.insert(Loc{0, pos.Y + 1}, la.lines[pos.Y][pos.X:])
105         la.DeleteToEnd(Loc{pos.X, pos.Y})
106 }
107
108 // removes from start to end
109 func (la *LineArray) remove(start, end Loc) string {
110         sub := la.Substr(start, end)
111         startX := runeToByteIndex(start.X, la.lines[start.Y])
112         endX := runeToByteIndex(end.X, la.lines[end.Y])
113         if start.Y == end.Y {
114                 la.lines[start.Y] = append(la.lines[start.Y][:startX], la.lines[start.Y][endX:]...)
115         } else {
116                 for i := start.Y + 1; i <= end.Y-1; i++ {
117                         la.DeleteLine(start.Y + 1)
118                 }
119                 la.DeleteToEnd(Loc{startX, start.Y})
120                 la.DeleteFromStart(Loc{endX - 1, start.Y + 1})
121                 la.JoinLines(start.Y, start.Y+1)
122         }
123         return sub
124 }
125
126 // DeleteToEnd deletes from the end of a line to the position
127 func (la *LineArray) DeleteToEnd(pos Loc) {
128         la.lines[pos.Y] = la.lines[pos.Y][:pos.X]
129 }
130
131 // DeleteFromStart deletes from the start of a line to the position
132 func (la *LineArray) DeleteFromStart(pos Loc) {
133         la.lines[pos.Y] = la.lines[pos.Y][pos.X+1:]
134 }
135
136 // DeleteLine deletes the line number
137 func (la *LineArray) DeleteLine(y int) {
138         la.lines = la.lines[:y+copy(la.lines[y:], la.lines[y+1:])]
139 }
140
141 // DeleteByte deletes the byte at a position
142 func (la *LineArray) DeleteByte(pos Loc) {
143         la.lines[pos.Y] = la.lines[pos.Y][:pos.X+copy(la.lines[pos.Y][pos.X:], la.lines[pos.Y][pos.X+1:])]
144 }
145
146 // Substr returns the string representation between two locations
147 func (la *LineArray) Substr(start, end Loc) string {
148         startX := runeToByteIndex(start.X, la.lines[start.Y])
149         endX := runeToByteIndex(end.X, la.lines[end.Y])
150         if start.Y == end.Y {
151                 return string(la.lines[start.Y][startX:endX])
152         }
153         var str string
154         str += string(la.lines[start.Y][startX:]) + "\n"
155         for i := start.Y + 1; i <= end.Y-1; i++ {
156                 str += string(la.lines[i]) + "\n"
157         }
158         str += string(la.lines[end.Y][:endX])
159         return str
160 }