]> git.lizzy.rs Git - micro.git/blob - cmd/micro/search.go
Merge pull request #782 from i-amdroid/master
[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.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEnter:
68                         // Done
69                         EndSearch()
70                         return
71                 }
72         }
73
74         messenger.HandleEvent(event, searchHistory)
75
76         if messenger.cursorx < 0 {
77                 // Done
78                 EndSearch()
79                 return
80         }
81
82         if messenger.response == "" {
83                 v.Cursor.ResetSelection()
84                 // We don't end the search though
85                 return
86         }
87
88         Search(messenger.response, v, true)
89
90         v.Relocate()
91
92         return
93 }
94
95 func searchDown(r *regexp.Regexp, v *View, start, end Loc) bool {
96         for i := start.Y; i <= end.Y; i++ {
97                 var l []byte
98                 var charPos int
99                 if i == start.Y {
100                         runes := []rune(string(v.Buf.lines[i].data))
101                         l = []byte(string(runes[start.X:]))
102                         charPos = start.X
103
104                         if strings.Contains(r.String(), "^") && start.X != 0 {
105                                 continue
106                         }
107                 } else {
108                         l = v.Buf.lines[i].data
109                 }
110
111                 match := r.FindIndex(l)
112
113                 if match != nil {
114                         v.Cursor.SetSelectionStart(Loc{charPos + runePos(match[0], string(l)), i})
115                         v.Cursor.SetSelectionEnd(Loc{charPos + runePos(match[1], string(l)), i})
116                         v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
117                         v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
118                         v.Cursor.Loc = v.Cursor.CurSelection[1]
119
120                         return true
121                 }
122         }
123         return false
124 }
125
126 func searchUp(r *regexp.Regexp, v *View, start, end Loc) bool {
127         for i := start.Y; i >= end.Y; i-- {
128                 var l []byte
129                 if i == start.Y {
130                         runes := []rune(string(v.Buf.lines[i].data))
131                         l = []byte(string(runes[:start.X]))
132
133                         if strings.Contains(r.String(), "$") && start.X != Count(string(l)) {
134                                 continue
135                         }
136                 } else {
137                         l = v.Buf.lines[i].data
138                 }
139
140                 match := r.FindIndex(l)
141
142                 if match != nil {
143                         v.Cursor.SetSelectionStart(Loc{runePos(match[0], string(l)), i})
144                         v.Cursor.SetSelectionEnd(Loc{runePos(match[1], string(l)), i})
145                         v.Cursor.OrigSelection[0] = v.Cursor.CurSelection[0]
146                         v.Cursor.OrigSelection[1] = v.Cursor.CurSelection[1]
147                         v.Cursor.Loc = v.Cursor.CurSelection[1]
148
149                         return true
150                 }
151         }
152         return false
153 }
154
155 // Search searches in the view for the given regex. The down bool
156 // specifies whether it should search down from the searchStart position
157 // or up from there
158 func Search(searchStr string, v *View, down bool) {
159         if searchStr == "" {
160                 return
161         }
162         r, err := regexp.Compile(searchStr)
163         if v.Buf.Settings["ignorecase"].(bool) {
164                 r, err = regexp.Compile("(?i)" + searchStr)
165         }
166         if err != nil {
167                 return
168         }
169
170         var found bool
171         if down {
172                 found = searchDown(r, v, searchStart, v.Buf.End())
173                 if !found {
174                         found = searchDown(r, v, v.Buf.Start(), searchStart)
175                 }
176         } else {
177                 found = searchUp(r, v, searchStart, v.Buf.Start())
178                 if !found {
179                         found = searchUp(r, v, v.Buf.End(), searchStart)
180                 }
181         }
182         if found {
183                 lastSearch = searchStr
184         } else {
185                 v.Cursor.ResetSelection()
186         }
187 }