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 |