-
Notifications
You must be signed in to change notification settings - Fork 8
/
findreplace.go
206 lines (187 loc) · 5.19 KB
/
findreplace.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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// Copyright 2013 The lime Authors.
// Use of this source code is governed by a 2-clause
// BSD-style license that can be found in the LICENSE file.
package commands
import (
"errors"
"github.com/limetext/backend"
"github.com/limetext/text"
)
type (
// FindUnderExpand Command extends the selection to the current word
// if the current selection region is empty.
// If one character or more is selected, the text buffer is scanned for
// the next occurrence of the selection and that region too is added to
// the selection set.
FindUnderExpand struct {
backend.DefaultCommand
}
// FindNext command searches for the last search term, starting at
// the end of the last selection in the buffer, and wrapping around. If
// it finds the term, it clears the current selections and selects the
// newly-found regions.
FindNext struct {
backend.DefaultCommand
}
// ReplaceNext Command searches for the "old" argument text,
// and at the first occurance of the text, replaces it with the
// "new" argument text. If there are multiple regions, the find
// starts from the max region.
ReplaceNext struct {
backend.DefaultCommand
}
FindAll struct {
backend.DefaultCommand
SearchText []rune
}
ReplaceAll struct {
backend.DefaultCommand
SearchText []rune
ReplaceText []rune
}
)
var (
// Remembers the last sequence of runes searched for.
lastSearch []rune
replaceText string
)
// Run executes the FindUnderExpand command.
func (c *FindUnderExpand) Run(v *backend.View, e *backend.Edit) error {
sel := v.Sel()
rs := sel.Regions()
if sel.HasEmpty() {
for i, r := range rs {
if r2 := v.Word(r.A); r2.Size() > r.Size() {
rs[i] = r2
}
}
sel.Clear()
sel.AddAll(rs)
lastSearch = v.SubstrR(rs[len(rs)-1])
return nil
}
last := rs[len(rs)-1]
lastSearch = v.SubstrR(last)
r := v.Find(string(lastSearch), last.End(), backend.IGNORECASE|backend.LITERAL)
if r.A != -1 {
sel.Add(r)
}
return nil
}
func nextSelection(v *backend.View, search string) (text.Region, error) {
sel := v.Sel()
rs := sel.Regions()
last := 0
wrap := v.Settings().Bool("find_wrap")
// Regions are not sorted, so finding the last one requires a search.
for _, r := range rs {
last = text.Max(last, r.End())
}
// Start the search right after the last selection.
start := last
r := v.Find(search, start, backend.IGNORECASE|backend.LITERAL)
// If not found yet and find_wrap setting is true, search
// from the start of the buffer to our original starting point.
if r.A == -1 && wrap {
r = v.Find(search, 0, backend.IGNORECASE|backend.LITERAL)
}
// If we found our string, select it.
if r.A != -1 {
return r, nil
}
return text.Region{-1, -1}, errors.New("Selection not Found")
}
func (c *FindAll) Run(v *backend.View, e *backend.Edit) error {
if len(c.SearchText) == 0 {
return nil
}
/* Original state of find_wrap is stored and then is made false so that nextSection doesn't go into an infinate loop.
Later in the end, it is returned to it's original state.
*/
wrap := v.Settings().Bool("find_wrap")
v.Settings().Set("find_wrap", false)
search := string(c.SearchText)
sel := v.Sel()
sel.Clear()
for {
selection, err := nextSelection(v, search)
if err != nil {
break
}
sel.Add(selection)
}
v.Settings().Set("find_wrap", wrap)
return nil
}
// Run executes the FindNext command.
func (c *FindNext) Run(v *backend.View, e *backend.Edit) error {
/*
Correct behavior of FindNext:
- If there is no previous search, do nothing
- Find the last region in the buffer, start the
search immediately after that.
- If the search term is found, clear any existing
selections, and select the newly-found region.
- Right now this is doing a case-sensitive search. In ST3
that's a setting.
*/
// If there is no last search term, nothing to do here.
if len(lastSearch) == 0 {
return nil
}
newr, err := nextSelection(v, string(lastSearch))
if err != nil {
return err
}
sel := v.Sel()
sel.Clear()
sel.Add(newr)
return nil
}
func (c *ReplaceAll) Run(v *backend.View, e *backend.Edit) error {
if len(c.SearchText) == 0 {
return nil
}
search := string(c.SearchText)
replace := string(c.ReplaceText)
sel := v.Sel()
sel.Clear()
/* Original state of find_wrap is stored and then is made false so that nextSection doesn't go into an infinate loop.
Later in the end, it is returned to it's original state.
*/
wrap := v.Settings().Bool("find_wrap")
v.Settings().Set("find_wrap", false)
for {
selection, err := nextSelection(v, string(search))
if err != nil {
break
}
v.Erase(e, selection)
v.Insert(e, selection.Begin(), replace)
sel := v.Sel()
sel.Clear()
sel.Add(text.Region{selection.Begin(), selection.Begin() + len(replace)})
}
v.Settings().Set("find_wrap", wrap)
return nil
}
// Run executes the ReplaceNext command.
func (c *ReplaceNext) Run(v *backend.View, e *backend.Edit) error {
// use selection function from find.go to get the next region
selection, err := nextSelection(v, string(lastSearch))
if err != nil {
return err
}
v.Erase(e, selection)
v.Insert(e, selection.Begin(), replaceText)
return nil
}
func init() {
register([]backend.Command{
&FindUnderExpand{},
&FindNext{},
&ReplaceNext{},
&ReplaceAll{},
&FindAll{},
})
}