]> git.lizzy.rs Git - micro.git/blob - cmd/micro/search.go
Merge pull request #1200 from Calinou/add-systemd-timer-section
[micro.git] / cmd / micro / search.go
1 package main
2
3 import (
4         "regexp"
5         "strings"
6
7         "github.com/zyedidia/tcell"
8 )
9
10 var (
11         // What was the last search
12         lastSearch string
13
14         // Where should we start the search down from (or up from)
15         searchStart Loc
16
17         // Is there currently a search in progress
18         searching bool
19
20         // Stores the history for searching
21         searchHistory []string
22 )
23
24 // BeginSearch starts a search
25 func BeginSearch(searchStr string) {
26         searchHistory = append(searchHistory, "")
27         messenger.historyNum = len(searchHistory) - 1
28         searching = true
29         messenger.response = searchStr
30         messenger.cursorx = Count(searchStr)
31         messenger.Message("Find: ")
32         messenger.hasPrompt = true
33 }
34
35 // EndSearch stops the current search
36 func EndSearch() {
37         searchHistory[len(searchHistory)-1] = messenger.response
38         searching = false
39         messenger.hasPrompt = false
40         messenger.Clear()
41         messenger.Reset()
42         if lastSearch != "" {
43                 messenger.Message("^P Previous ^N Next")
44         }
45 }
46
47 // ExitSearch exits the search mode, reset active search phrase, and clear status bar
48 func ExitSearch(v *View) {
49         lastSearch = ""
50         searching = false
51         messenger.hasPrompt = false
52         messenger.Clear()
53         messenger.Reset()
54         v.Cursor.ResetSelection()
55 }
56
57 // HandleSearchEvent takes an event and a view and will do a real time match from the messenger's output
58 // to the current buffer. It searches down the buffer.
59 func HandleSearchEvent(event tcell.Event, v *View) {
60         switch e := event.(type) {
61         case *tcell.EventKey:
62                 switch e.Key() {
63                 case tcell.KeyEscape:
64                         // Exit the search mode
65                         ExitSearch(v)
66                         return
67                 case tcell.KeyEnter:
68                         // If the user has pressed Enter, they want this to be the lastSearch
69                         lastSearch = messenger.response
70                         EndSearch()
71                         return
72                 case tcell.KeyCtrlQ, tcell.KeyCtrlC:
73                         // Done
74                         EndSearch()
75                         return
76                 }
77         }
78
79         messenger.HandleEvent(event, searchHistory)
80
81         if messenger.cursorx < 0 {
82                 // Done
83                 EndSearch()
84                 return
85         }
86
87         if messenger.response == "" {
88                 v.Cursor.ResetSelection()
89                 // We don't end the search though
90                 return
91         }
92
93         Search(messenger.response, v, true)
94
95         v.Relocate()
96
97         return
98 }
99
100 func searchDown(r *regexp.Regexp, v *View, start, end Loc) bool {
101         if start.Y >= v.Buf.NumLines {
102                 start.Y = v.Buf.NumLines - 1
103         }
104         if start.Y < 0 {
105                 start.Y = 0
106         }
107         for i := start.Y; i <= end.Y; i++ {
108                 var l []byte
109                 var charPos int
110                 if i == start.Y {
111                         runes := []rune(string(v.Buf.lines[i].data))
112                         if start.X >= len(runes) {
113                                 start.X = len(runes) - 1
114                         }
115                         if start.X < 0 {
116                                 start.X = 0
117                         }
118                         l = []byte(string(runes[start.X:]))
119                         charPos = start.X
120
121                         if strings.Contains(r.String(), "^") && start.X != 0 {
122                                 continue
123                         }
124                 } else {
125                         l = v.Buf.lines[i].data
126                 }
127
128                 match := r.FindIndex(l)
129
130                 if match != nil {
131                         v.Cursor.SetSelectionStart(Loc{charPos + runePos(match[0], string(l)), i})
132                         v.Cursor.SetSelectionEnd(Loc{charPos + runePos(match[1], string(l)), i})
133                         v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
134                         v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
135                         v.Cursor.Loc = v.Cursor.CurSelection[1]
136
137                         return true
138                 }
139         }
140         return false
141 }
142
143 func searchUp(r *regexp.Regexp, v *View, start, end Loc) bool {
144         if start.Y >= v.Buf.NumLines {
145                 start.Y = v.Buf.NumLines - 1
146         }
147         if start.Y < 0 {
148                 start.Y = 0
149         }
150         for i := start.Y; i >= end.Y; i-- {
151                 var l []byte
152                 if i == start.Y {
153                         runes := []rune(string(v.Buf.lines[i].data))
154                         if start.X >= len(runes) {
155                                 start.X = len(runes) - 1
156                         }
157                         if start.X < 0 {
158                                 start.X = 0
159                         }
160                         l = []byte(string(runes[:start.X]))
161
162                         if strings.Contains(r.String(), "$") && start.X != Count(string(l)) {
163                                 continue
164                         }
165                 } else {
166                         l = v.Buf.lines[i].data
167                 }
168
169                 match := r.FindIndex(l)
170
171                 if match != nil {
172                         v.Cursor.SetSelectionStart(Loc{runePos(match[0], string(l)), i})
173                         v.Cursor.SetSelectionEnd(Loc{runePos(match[1], string(l)), i})
174                         v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
175                         v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
176                         v.Cursor.Loc = v.Cursor.CurSelection[1]
177
178                         return true
179                 }
180         }
181         return false
182 }
183
184 // Search searches in the view for the given regex. The down bool
185 // specifies whether it should search down from the searchStart position
186 // or up from there
187 func Search(searchStr string, v *View, down bool) {
188         if searchStr == "" {
189                 return
190         }
191         r, err := regexp.Compile(searchStr)
192         if v.Buf.Settings["ignorecase"].(bool) {
193                 r, err = regexp.Compile("(?i)" + searchStr)
194         }
195         if err != nil {
196                 return
197         }
198
199         var found bool
200         if down {
201                 found = searchDown(r, v, searchStart, v.Buf.End())
202                 if !found {
203                         found = searchDown(r, v, v.Buf.Start(), searchStart)
204                 }
205         } else {
206                 found = searchUp(r, v, searchStart, v.Buf.Start())
207                 if !found {
208                         found = searchUp(r, v, v.Buf.End(), searchStart)
209                 }
210         }
211         if !found {
212                 v.Cursor.ResetSelection()
213         }
214 }