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