]> git.lizzy.rs Git - micro.git/blob - internal/buffer/search.go
Improve comments
[micro.git] / internal / buffer / search.go
1 package buffer
2
3 import (
4         "regexp"
5
6         "github.com/zyedidia/micro/v2/internal/util"
7 )
8
9 func (b *Buffer) findDown(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
10         lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
11         if start.Y > b.LinesNum()-1 {
12                 start.X = lastcn - 1
13         }
14         if end.Y > b.LinesNum()-1 {
15                 end.X = lastcn
16         }
17         start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
18         end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
19
20         if start.GreaterThan(end) {
21                 start, end = end, start
22         }
23
24         for i := start.Y; i <= end.Y; i++ {
25                 l := b.LineBytes(i)
26                 charpos := 0
27
28                 if i == start.Y && start.Y == end.Y {
29                         nchars := util.CharacterCount(l)
30                         start.X = util.Clamp(start.X, 0, nchars)
31                         end.X = util.Clamp(end.X, 0, nchars)
32                         l = util.SliceStart(l, end.X)
33                         l = util.SliceEnd(l, start.X)
34                         charpos = start.X
35                 } else if i == start.Y {
36                         nchars := util.CharacterCount(l)
37                         start.X = util.Clamp(start.X, 0, nchars)
38                         l = util.SliceEnd(l, start.X)
39                         charpos = start.X
40                 } else if i == end.Y {
41                         nchars := util.CharacterCount(l)
42                         end.X = util.Clamp(end.X, 0, nchars)
43                         l = util.SliceStart(l, end.X)
44                 }
45
46                 match := r.FindIndex(l)
47
48                 if match != nil {
49                         start := Loc{charpos + util.RunePos(l, match[0]), i}
50                         end := Loc{charpos + util.RunePos(l, match[1]), i}
51                         return [2]Loc{start, end}, true
52                 }
53         }
54         return [2]Loc{}, false
55 }
56
57 func (b *Buffer) findUp(r *regexp.Regexp, start, end Loc) ([2]Loc, bool) {
58         lastcn := util.CharacterCount(b.LineBytes(b.LinesNum() - 1))
59         if start.Y > b.LinesNum()-1 {
60                 start.X = lastcn - 1
61         }
62         if end.Y > b.LinesNum()-1 {
63                 end.X = lastcn
64         }
65         start.Y = util.Clamp(start.Y, 0, b.LinesNum()-1)
66         end.Y = util.Clamp(end.Y, 0, b.LinesNum()-1)
67
68         if start.GreaterThan(end) {
69                 start, end = end, start
70         }
71
72         for i := end.Y; i >= start.Y; i-- {
73                 l := b.LineBytes(i)
74                 charpos := 0
75
76                 if i == start.Y && start.Y == end.Y {
77                         nchars := util.CharacterCount(l)
78                         start.X = util.Clamp(start.X, 0, nchars)
79                         end.X = util.Clamp(end.X, 0, nchars)
80                         l = util.SliceStart(l, end.X)
81                         l = util.SliceEnd(l, start.X)
82                         charpos = start.X
83                 } else if i == start.Y {
84                         nchars := util.CharacterCount(l)
85                         start.X = util.Clamp(start.X, 0, nchars)
86                         l = util.SliceEnd(l, start.X)
87                         charpos = start.X
88                 } else if i == end.Y {
89                         nchars := util.CharacterCount(l)
90                         end.X = util.Clamp(end.X, 0, nchars)
91                         l = util.SliceStart(l, end.X)
92                 }
93
94                 allMatches := r.FindAllIndex(l, -1)
95
96                 if allMatches != nil {
97                         match := allMatches[len(allMatches)-1]
98                         start := Loc{charpos + util.RunePos(l, match[0]), i}
99                         end := Loc{charpos + util.RunePos(l, match[1]), i}
100                         return [2]Loc{start, end}, true
101                 }
102         }
103         return [2]Loc{}, false
104 }
105
106 // FindNext finds the next occurrence of a given string in the buffer
107 // It returns the start and end location of the match (if found) and
108 // a boolean indicating if it was found
109 // May also return an error if the search regex is invalid
110 func (b *Buffer) FindNext(s string, start, end, from Loc, down bool, useRegex bool) ([2]Loc, bool, error) {
111         if s == "" {
112                 return [2]Loc{}, false, nil
113         }
114
115         var r *regexp.Regexp
116         var err error
117
118         if !useRegex {
119                 s = regexp.QuoteMeta(s)
120         }
121
122         if b.Settings["ignorecase"].(bool) {
123                 r, err = regexp.Compile("(?i)" + s)
124         } else {
125                 r, err = regexp.Compile(s)
126         }
127
128         if err != nil {
129                 return [2]Loc{}, false, err
130         }
131
132         var found bool
133         var l [2]Loc
134         if down {
135                 l, found = b.findDown(r, from, end)
136                 if !found {
137                         l, found = b.findDown(r, start, end)
138                 }
139         } else {
140                 l, found = b.findUp(r, from, start)
141                 if !found {
142                         l, found = b.findUp(r, end, start)
143                 }
144         }
145         return l, found, nil
146 }
147
148 // ReplaceRegex replaces all occurrences of 'search' with 'replace' in the given area
149 // and returns the number of replacements made and the number of runes
150 // added or removed on the last line of the range
151 func (b *Buffer) ReplaceRegex(start, end Loc, search *regexp.Regexp, replace []byte) (int, int) {
152         if start.GreaterThan(end) {
153                 start, end = end, start
154         }
155
156         netrunes := 0
157
158         found := 0
159         var deltas []Delta
160         for i := start.Y; i <= end.Y; i++ {
161                 l := b.lines[i].data
162                 charpos := 0
163
164                 if start.Y == end.Y && i == start.Y {
165                         l = util.SliceStart(l, end.X)
166                         l = util.SliceEnd(l, start.X)
167                         charpos = start.X
168                 } else if i == start.Y {
169                         l = util.SliceEnd(l, start.X)
170                         charpos = start.X
171                 } else if i == end.Y {
172                         l = util.SliceStart(l, end.X)
173                 }
174                 newText := search.ReplaceAllFunc(l, func(in []byte) []byte {
175                         result := []byte{}
176                         for _, submatches := range search.FindAllSubmatchIndex(in, -1) {
177                                 result = search.Expand(result, replace, in, submatches)
178                         }
179                         found++
180                         if i == end.Y {
181                                 netrunes += util.CharacterCount(result) - util.CharacterCount(in)
182                         }
183                         return result
184                 })
185
186                 from := Loc{charpos, i}
187                 to := Loc{charpos + util.CharacterCount(l), i}
188
189                 deltas = append(deltas, Delta{newText, from, to})
190         }
191         b.MultipleReplace(deltas)
192
193         return found, netrunes
194 }