OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 List libraryList; | |
6 InputElement searchInput; | |
7 DivElement dropdown; | |
8 | |
9 /** | |
10 * Update the search drop down based on the current search text. | |
11 */ | |
12 updateDropDown(Event event) { | |
13 if (libraryList == null) return; | |
14 if (searchInput == null) return; | |
15 if (dropdown == null) return; | |
16 | |
17 var results = <Result>[]; | |
18 String text = searchInput.value; | |
19 if (text == currentSearchText) { | |
20 return; | |
21 } | |
22 if (text.isEmpty()) { | |
23 updateResults(text, results); | |
24 hideDropDown(); | |
25 return; | |
26 } | |
27 if (text.contains('.')) { | |
28 // Search type members. | |
29 String typeText = text.substring(0, text.indexOf('.')); | |
30 String memberText = text.substring(text.indexOf('.') + 1); | |
31 | |
32 if (typeText.isEmpty() && memberText.isEmpty()) { | |
33 // Don't search on '.'. | |
34 } else if (typeText.isEmpty()) { | |
35 // Search text is of the form '.id' => Look up members. | |
36 matchAllMembers(results, memberText); | |
37 } else if (memberText.isEmpty()) { | |
38 // Search text is of the form 'Type.' => Look up members in 'Type'. | |
39 matchAllMembersInType(results, typeText, memberText); | |
40 } else { | |
41 // Search text is of the form 'Type.id' => Look up member 'id' in 'Type'. | |
42 matchMembersInType(results, text, typeText, memberText); | |
43 } | |
44 } else { | |
45 // Search all entities. | |
46 var searchText = new SearchText(text); | |
47 for (Map<String,Dynamic> library in libraryList) { | |
48 matchLibrary(results, searchText, library); | |
49 matchLibraryMembers(results, searchText, library); | |
50 matchTypes(results, searchText, library); | |
51 } | |
52 } | |
53 var elements = <Element>[]; | |
54 var table = new TableElement(); | |
55 table.classes.add('drop-down-table'); | |
56 elements.add(table); | |
57 | |
58 if (results.isEmpty()) { | |
59 var row = table.insertRow(0); | |
60 row.innerHTML = "<tr><td>No matches found for '$text'.</td></tr>"; | |
61 } else { | |
62 results.sort(resultComparator); | |
63 | |
64 var count = 0; | |
65 for (Result result in results) { | |
66 result.addRow(table); | |
67 if (++count >= 10) { | |
68 break; | |
69 } | |
70 } | |
71 if (results.length >= 10) { | |
72 var row = table.insertRow(table.rows.length); | |
73 row.innerHTML = '<tr><td>+ ${results.length-10} more.</td></tr>'; | |
74 results = results.getRange(0, 10); | |
75 } | |
76 } | |
77 dropdown.elements = elements; | |
78 updateResults(text, results); | |
79 showDropDown(); | |
80 } | |
81 | |
82 void matchAllMembers(List<Result> results, String memberText) { | |
83 var searchText = new SearchText(memberText); | |
84 for (Map<String,Dynamic> library in libraryList) { | |
85 String libraryName = library[NAME]; | |
86 if (library.containsKey(TYPES)) { | |
87 for (Map<String,Dynamic> type in library[TYPES]) { | |
88 String typeName = type[NAME]; | |
89 if (type.containsKey(MEMBERS)) { | |
90 for (Map<String,Dynamic> member in type[MEMBERS]) { | |
91 StringMatch memberMatch = obtainMatch(searchText, member[NAME]); | |
92 if (memberMatch != null) { | |
93 results.add(new Result(memberMatch, member[KIND], | |
94 getTypeMemberUrl(libraryName, typeName, member), | |
95 library: libraryName, type: typeName, args: type[ARGS])); | |
96 } | |
97 } | |
98 } | |
99 } | |
100 } | |
101 } | |
102 } | |
103 | |
104 void matchAllMembersInType(List<Result> results, | |
105 String typeText, String memberText) { | |
106 var searchText = new SearchText(typeText); | |
107 var emptyText = new SearchText(memberText); | |
108 for (Map<String,Dynamic> library in libraryList) { | |
109 String libraryName = library[NAME]; | |
110 if (library.containsKey(TYPES)) { | |
111 for (Map<String,Dynamic> type in library[TYPES]) { | |
112 String typeName = type[NAME]; | |
113 StringMatch typeMatch = obtainMatch(searchText, typeName); | |
114 if (typeMatch != null) { | |
115 if (type.containsKey(MEMBERS)) { | |
116 for (Map<String,Dynamic> member in type[MEMBERS]) { | |
117 StringMatch memberMatch = obtainMatch(emptyText, | |
118 member[NAME]); | |
119 results.add(new Result(memberMatch, member[KIND], | |
120 getTypeMemberUrl(libraryName, typeName, member), | |
121 library: libraryName, prefix: typeMatch)); | |
122 } | |
123 } | |
124 } | |
125 } | |
126 } | |
127 } | |
128 } | |
129 | |
130 void matchMembersInType(List<Result> results, | |
131 String text, String typeText, String memberText) { | |
132 var searchText = new SearchText(text); | |
133 var typeSearchText = new SearchText(typeText); | |
134 var memberSearchText = new SearchText(memberText); | |
135 for (Map<String,Dynamic> library in libraryList) { | |
136 String libraryName = library[NAME]; | |
137 if (library.containsKey(TYPES)) { | |
138 for (Map<String,Dynamic> type in library[TYPES]) { | |
139 String typeName = type[NAME]; | |
140 StringMatch typeMatch = obtainMatch(typeSearchText, typeName); | |
141 if (typeMatch != null) { | |
142 if (type.containsKey(MEMBERS)) { | |
143 for (Map<String,Dynamic> member in type[MEMBERS]) { | |
144 // Check for constructor match. | |
145 StringMatch constructorMatch = obtainMatch(searchText, | |
146 member[NAME]); | |
147 if (constructorMatch != null) { | |
148 results.add(new Result(constructorMatch, member[KIND], | |
149 getTypeMemberUrl(libraryName, typeName, member), | |
150 library: libraryName)); | |
151 } else { | |
152 // Try member match. | |
153 StringMatch memberMatch = obtainMatch(memberSearchText, | |
154 member[NAME]); | |
155 if (memberMatch != null) { | |
156 results.add(new Result(memberMatch, member[KIND], | |
157 getTypeMemberUrl(libraryName, typeName, member), | |
158 library: libraryName, prefix: typeMatch, | |
159 args: type[ARGS])); | |
160 } | |
161 } | |
162 } | |
163 } | |
164 } | |
165 } | |
166 } | |
167 } | |
168 } | |
169 | |
170 void matchLibrary(List<Result> results, SearchText searchText, Map library) { | |
171 String libraryName = library[NAME]; | |
172 StringMatch libraryMatch = obtainMatch(searchText, libraryName); | |
173 if (libraryMatch != null) { | |
174 results.add(new Result(libraryMatch, LIBRARY, | |
175 getLibraryUrl(libraryName))); | |
176 } | |
177 } | |
178 | |
179 void matchLibraryMembers(List<Result> results, SearchText searchText, | |
180 Map library) { | |
181 if (library.containsKey(MEMBERS)) { | |
182 String libraryName = library[NAME]; | |
183 for (Map<String,Dynamic> member in library[MEMBERS]) { | |
184 StringMatch memberMatch = obtainMatch(searchText, member[NAME]); | |
185 if (memberMatch != null) { | |
186 results.add(new Result(memberMatch, member[KIND], | |
187 getLibraryMemberUrl(libraryName, member), | |
188 library: libraryName)); | |
189 } | |
190 } | |
191 } | |
192 } | |
193 | |
194 void matchTypes(List<Result> results, SearchText searchText, | |
195 Map library) { | |
196 if (library.containsKey(TYPES)) { | |
197 String libraryName = library[NAME]; | |
198 for (Map<String,Dynamic> type in library[TYPES]) { | |
199 String typeName = type[NAME]; | |
200 matchType(results, searchText, libraryName, type); | |
201 matchTypeMembers(results, searchText, libraryName, type); | |
202 } | |
203 } | |
204 } | |
205 | |
206 void matchType(List<Result> results, SearchText searchText, | |
207 String libraryName, Map type) { | |
208 String typeName = type[NAME]; | |
209 StringMatch typeMatch = obtainMatch(searchText, typeName); | |
210 if (typeMatch != null) { | |
211 results.add(new Result(typeMatch, type[KIND], | |
212 getTypeUrl(libraryName, type), | |
213 library: libraryName, args: type[ARGS])); | |
214 } | |
215 } | |
216 | |
217 void matchTypeMembers(List<Result> results, SearchText searchText, | |
218 String libraryName, Map type) { | |
219 if (type.containsKey(MEMBERS)) { | |
220 String typeName = type[NAME]; | |
221 for (Map<String,Dynamic> member in type[MEMBERS]) { | |
222 StringMatch memberMatch = obtainMatch(searchText, member[NAME]); | |
223 if (memberMatch != null) { | |
224 results.add(new Result(memberMatch, member[KIND], | |
225 getTypeMemberUrl(libraryName, typeName, member), | |
226 library: libraryName, type: typeName, args: type[ARGS])); | |
227 } | |
228 } | |
229 } | |
230 } | |
231 | |
232 String currentSearchText; | |
233 Result _currentResult; | |
234 List<Result> currentResults = const <Result>[]; | |
235 | |
236 void updateResults(String searchText, List<Result> results) { | |
237 currentSearchText = searchText; | |
238 currentResults = results; | |
239 if (currentResults.isEmpty()) { | |
240 _currentResultIndex = -1; | |
241 currentResult = null; | |
242 } else { | |
243 _currentResultIndex = 0; | |
244 currentResult = currentResults[0]; | |
245 } | |
246 } | |
247 | |
248 int _currentResultIndex; | |
249 | |
250 void set currentResultIndex(int index) { | |
251 if (index < -1) { | |
252 return; | |
253 } | |
254 if (index >= currentResults.length) { | |
255 return; | |
256 } | |
257 if (index != _currentResultIndex) { | |
258 _currentResultIndex = index; | |
259 if (index >= 0) { | |
260 currentResult = currentResults[_currentResultIndex]; | |
261 } else { | |
262 currentResult = null; | |
263 } | |
264 } | |
265 } | |
266 | |
267 int get currentResultIndex => _currentResultIndex; | |
268 | |
269 void set currentResult(Result result) { | |
270 if (_currentResult != result) { | |
271 if (_currentResult != null) { | |
272 _currentResult.row.classes.remove('drop-down-link-select'); | |
273 } | |
274 _currentResult = result; | |
275 if (_currentResult != null) { | |
276 _currentResult.row.classes.add('drop-down-link-select'); | |
277 } | |
278 } | |
279 } | |
280 | |
281 Result get currentResult => _currentResult; | |
282 | |
283 /** | |
284 * Navigate the search drop down using up/down inside the search field. Follow | |
285 * the result link on enter. | |
286 */ | |
287 void handleUpDown(KeyboardEvent event) { | |
288 if (event.keyIdentifier == KeyName.UP) { | |
289 currentResultIndex--; | |
290 event.preventDefault(); | |
291 } else if (event.keyIdentifier == KeyName.DOWN) { | |
292 currentResultIndex++; | |
293 event.preventDefault(); | |
294 } else if (event.keyIdentifier == KeyName.ENTER) { | |
295 if (currentResult != null) { | |
296 window.location.href = currentResult.url; | |
297 event.preventDefault(); | |
298 hideDropDown(); | |
299 } | |
300 } | |
301 } | |
302 | |
303 /** Show the search drop down unless there are no current results. */ | |
304 void showDropDown() { | |
305 if (currentResults.isEmpty()) { | |
306 hideDropDown(); | |
307 } else { | |
308 dropdown.style.visibility = 'visible'; | |
309 } | |
310 } | |
311 | |
312 /** Used to prevent hiding the drop down when it is clicked. */ | |
313 bool hideDropDownSuspend = false; | |
314 | |
315 /** Hide the search drop down unless suspended. */ | |
316 void hideDropDown() { | |
317 if (hideDropDownSuspend) return; | |
318 | |
319 dropdown.style.visibility = 'hidden'; | |
320 } | |
321 | |
322 /** Activate search on Ctrl+F and F3. */ | |
323 void shortcutHandler(KeyboardEvent event) { | |
324 if (event.keyCode == 0x46/*F*/ && event.ctrlKey || | |
325 event.keyIdentifier == KeyName.F3) { | |
326 searchInput.focus(); | |
327 event.preventDefault(); | |
328 } | |
329 } | |
330 | |
331 /** Setup search hooks. */ | |
332 void setupSearch(var libraries) { | |
333 libraryList = libraries; | |
334 searchInput = query('#q'); | |
335 dropdown = query('#drop-down'); | |
336 | |
337 searchInput.on.keyDown.add(handleUpDown); | |
338 searchInput.on.keyUp.add(updateDropDown); | |
339 searchInput.on.change.add(updateDropDown); | |
340 searchInput.on.reset.add(updateDropDown); | |
341 searchInput.on.focus.add((event) => showDropDown()); | |
342 searchInput.on.blur.add((event) => hideDropDown()); | |
343 window.on.keyDown.add(shortcutHandler); | |
344 } | |
OLD | NEW |