]> git.lizzy.rs Git - micro.git/blob - cmd/micro/search.go
Code optimisation (#1117)
[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         for i := start.Y; i <= end.Y; i++ {
102                 var l []byte
103                 var charPos int
104                 if i == start.Y {
105                         runes := []rune(string(v.Buf.lines[i].data))
106                         l = []byte(string(runes[start.X:]))
107                         charPos = start.X
108
109                         if strings.Contains(r.String(), "^") && start.X != 0 {
110                                 continue
111                         }
112                 } else {
113                         l = v.Buf.lines[i].data
114                 }
115
116                 match := r.FindIndex(l)
117
118                 if match != nil {
119                         v.Cursor.SetSelectionStart(Loc{charPos + runePos(match[0], string(l)), i})
120                         v.Cursor.SetSelectionEnd(Loc{charPos + runePos(match[1], string(l)), i})
121                         v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
122                         v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
123                         v.Cursor.Loc = v.Cursor.CurSelection[1]
124
125                         return true
126                 }
127         }
128         return false
129 }
130
131 func searchUp(r *regexp.Regexp, v *View, start, end Loc) bool {
132         for i := start.Y; i >= end.Y; i-- {
133                 var l []byte
134                 if i == start.Y {
135                         runes := []rune(string(v.Buf.lines[i].data))
136                         l = []byte(string(runes[:start.X]))
137
138                         if strings.Contains(r.String(), "$") && start.X != Count(string(l)) {
139                                 continue
140                         }
141                 } else {
142                         l = v.Buf.lines[i].data
143                 }
144
145                 match := r.FindIndex(l)
146
147                 if match != nil {
148                         v.Cursor.SetSelectionStart(Loc{runePos(match[0], string(l)), i})
149                         v.Cursor.SetSelectionEnd(Loc{runePos(match[1], string(l)), i})
150                         v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
151                         v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
152                         v.Cursor.Loc = v.Cursor.CurSelection[1]
153
154                         return true
155                 }
156         }
157         return false
158 }
159
160 // Search searches in the view for the given regex. The down bool
161 // specifies whether it should search down from the searchStart position
162 // or up from there
163 func Search(searchStr string, v *View, down bool) {
164         if searchStr == "" {
165                 return
166         }
167         r, err := regexp.Compile(searchStr)
168         if v.Buf.Settings["ignorecase"].(bool) {
169                 r, err = regexp.Compile("(?i)" + searchStr)
170         }
171         if err != nil {
172                 return
173         }
174
175         var found bool
176         if down {
177                 found = searchDown(r, v, searchStart, v.Buf.End())
178                 if !found {
179                         found = searchDown(r, v, v.Buf.Start(), searchStart)
180                 }
181         } else {
182                 found = searchUp(r, v, searchStart, v.Buf.Start())
183                 if !found {
184                         found = searchUp(r, v, v.Buf.End(), searchStart)
185                 }
186         }
187         if !found {
188                 v.Cursor.ResetSelection()
189         }
190 }