| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| part of client_live_nav; |
| |
| List libraryList; |
| InputElement searchInput; |
| DivElement dropdown; |
| |
| /** |
| * Update the search drop down based on the current search text. |
| */ |
| updateDropDown(Event event) { |
| if (libraryList == null) return; |
| if (searchInput == null) return; |
| if (dropdown == null) return; |
| |
| var results = <Result>[]; |
| String text = searchInput.value; |
| if (text == currentSearchText) { |
| return; |
| } |
| if (text.isEmpty) { |
| updateResults(text, results); |
| hideDropDown(); |
| return; |
| } |
| if (text.contains('.')) { |
| // Search type members. |
| String typeText = text.substring(0, text.indexOf('.')); |
| String memberText = text.substring(text.indexOf('.') + 1); |
| |
| if (typeText.isEmpty && memberText.isEmpty) { |
| // Don't search on '.'. |
| } else if (typeText.isEmpty) { |
| // Search text is of the form '.id' => Look up members. |
| matchAllMembers(results, memberText); |
| } else if (memberText.isEmpty) { |
| // Search text is of the form 'Type.' => Look up members in 'Type'. |
| matchAllMembersInType(results, typeText, memberText); |
| } else { |
| // Search text is of the form 'Type.id' => Look up member 'id' in 'Type'. |
| matchMembersInType(results, text, typeText, memberText); |
| } |
| } else { |
| // Search all entities. |
| var searchText = new SearchText(text); |
| for (Map<String,dynamic> library in libraryList) { |
| matchLibrary(results, searchText, library); |
| matchLibraryMembers(results, searchText, library); |
| matchTypes(results, searchText, library); |
| } |
| } |
| var elements = <Element>[]; |
| var table = new TableElement(); |
| table.classes.add('drop-down-table'); |
| elements.add(table); |
| |
| if (results.isEmpty) { |
| var row = table.insertRow(0); |
| row.innerHTML = "<tr><td>No matches found for '$text'.</td></tr>"; |
| } else { |
| results.sort(resultComparator); |
| |
| var count = 0; |
| for (Result result in results) { |
| result.addRow(table); |
| if (++count >= 10) { |
| break; |
| } |
| } |
| if (results.length >= 10) { |
| var row = table.insertRow(table.rows.length); |
| row.innerHTML = '<tr><td>+ ${results.length-10} more.</td></tr>'; |
| results = results.getRange(0, 10); |
| } |
| } |
| dropdown.elements = elements; |
| updateResults(text, results); |
| showDropDown(); |
| } |
| |
| void matchAllMembers(List<Result> results, String memberText) { |
| var searchText = new SearchText(memberText); |
| for (Map<String,dynamic> library in libraryList) { |
| String libraryName = library[NAME]; |
| if (library.containsKey(TYPES)) { |
| for (Map<String,dynamic> type in library[TYPES]) { |
| String typeName = type[NAME]; |
| if (type.containsKey(MEMBERS)) { |
| for (Map<String,dynamic> member in type[MEMBERS]) { |
| StringMatch memberMatch = obtainMatch(searchText, member[NAME]); |
| if (memberMatch != null) { |
| results.add(new Result(memberMatch, member[KIND], |
| getTypeMemberUrl(libraryName, typeName, member), |
| library: libraryName, type: typeName, args: type[ARGS], |
| noargs: member[NO_PARAMS])); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| void matchAllMembersInType(List<Result> results, |
| String typeText, String memberText) { |
| var searchText = new SearchText(typeText); |
| var emptyText = new SearchText(memberText); |
| for (Map<String,dynamic> library in libraryList) { |
| String libraryName = library[NAME]; |
| if (library.containsKey(TYPES)) { |
| for (Map<String,dynamic> type in library[TYPES]) { |
| String typeName = type[NAME]; |
| StringMatch typeMatch = obtainMatch(searchText, typeName); |
| if (typeMatch != null) { |
| if (type.containsKey(MEMBERS)) { |
| for (Map<String,dynamic> member in type[MEMBERS]) { |
| StringMatch memberMatch = obtainMatch(emptyText, |
| member[NAME]); |
| results.add(new Result(memberMatch, member[KIND], |
| getTypeMemberUrl(libraryName, typeName, member), |
| library: libraryName, prefix: typeMatch, |
| noargs: member[NO_PARAMS])); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| void matchMembersInType(List<Result> results, |
| String text, String typeText, String memberText) { |
| var searchText = new SearchText(text); |
| var typeSearchText = new SearchText(typeText); |
| var memberSearchText = new SearchText(memberText); |
| for (Map<String,dynamic> library in libraryList) { |
| String libraryName = library[NAME]; |
| if (library.containsKey(TYPES)) { |
| for (Map<String,dynamic> type in library[TYPES]) { |
| String typeName = type[NAME]; |
| StringMatch typeMatch = obtainMatch(typeSearchText, typeName); |
| if (typeMatch != null) { |
| if (type.containsKey(MEMBERS)) { |
| for (Map<String,dynamic> member in type[MEMBERS]) { |
| // Check for constructor match. |
| StringMatch constructorMatch = obtainMatch(searchText, |
| member[NAME]); |
| if (constructorMatch != null) { |
| results.add(new Result(constructorMatch, member[KIND], |
| getTypeMemberUrl(libraryName, typeName, member), |
| library: libraryName, noargs: member[NO_PARAMS])); |
| } else { |
| // Try member match. |
| StringMatch memberMatch = obtainMatch(memberSearchText, |
| member[NAME]); |
| if (memberMatch != null) { |
| results.add(new Result(memberMatch, member[KIND], |
| getTypeMemberUrl(libraryName, typeName, member), |
| library: libraryName, prefix: typeMatch, |
| args: type[ARGS], noargs: member[NO_PARAMS])); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| void matchLibrary(List<Result> results, SearchText searchText, Map library) { |
| String libraryName = library[NAME]; |
| StringMatch libraryMatch = obtainMatch(searchText, libraryName); |
| if (libraryMatch != null) { |
| results.add(new Result(libraryMatch, LIBRARY, |
| getLibraryUrl(libraryName))); |
| } |
| } |
| |
| void matchLibraryMembers(List<Result> results, SearchText searchText, |
| Map library) { |
| if (library.containsKey(MEMBERS)) { |
| String libraryName = library[NAME]; |
| for (Map<String,dynamic> member in library[MEMBERS]) { |
| StringMatch memberMatch = obtainMatch(searchText, member[NAME]); |
| if (memberMatch != null) { |
| results.add(new Result(memberMatch, member[KIND], |
| getLibraryMemberUrl(libraryName, member), |
| library: libraryName, noargs: member[NO_PARAMS])); |
| } |
| } |
| } |
| } |
| |
| void matchTypes(List<Result> results, SearchText searchText, |
| Map library) { |
| if (library.containsKey(TYPES)) { |
| String libraryName = library[NAME]; |
| for (Map<String,dynamic> type in library[TYPES]) { |
| String typeName = type[NAME]; |
| matchType(results, searchText, libraryName, type); |
| matchTypeMembers(results, searchText, libraryName, type); |
| } |
| } |
| } |
| |
| void matchType(List<Result> results, SearchText searchText, |
| String libraryName, Map type) { |
| String typeName = type[NAME]; |
| StringMatch typeMatch = obtainMatch(searchText, typeName); |
| if (typeMatch != null) { |
| results.add(new Result(typeMatch, type[KIND], |
| getTypeUrl(libraryName, type), |
| library: libraryName, args: type[ARGS])); |
| } |
| } |
| |
| void matchTypeMembers(List<Result> results, SearchText searchText, |
| String libraryName, Map type) { |
| if (type.containsKey(MEMBERS)) { |
| String typeName = type[NAME]; |
| for (Map<String,dynamic> member in type[MEMBERS]) { |
| StringMatch memberMatch = obtainMatch(searchText, member[NAME]); |
| if (memberMatch != null) { |
| results.add(new Result(memberMatch, member[KIND], |
| getTypeMemberUrl(libraryName, typeName, member), |
| library: libraryName, type: typeName, args: type[ARGS], |
| noargs: member[NO_PARAMS])); |
| } |
| } |
| } |
| } |
| |
| String currentSearchText; |
| Result _currentResult; |
| List<Result> currentResults = const <Result>[]; |
| |
| void updateResults(String searchText, List<Result> results) { |
| currentSearchText = searchText; |
| currentResults = results; |
| if (currentResults.isEmpty) { |
| _currentResultIndex = -1; |
| currentResult = null; |
| } else { |
| _currentResultIndex = 0; |
| currentResult = currentResults[0]; |
| } |
| } |
| |
| int _currentResultIndex; |
| |
| void set currentResultIndex(int index) { |
| if (index < -1) { |
| return; |
| } |
| if (index >= currentResults.length) { |
| return; |
| } |
| if (index != _currentResultIndex) { |
| _currentResultIndex = index; |
| if (index >= 0) { |
| currentResult = currentResults[_currentResultIndex]; |
| } else { |
| currentResult = null; |
| } |
| } |
| } |
| |
| int get currentResultIndex => _currentResultIndex; |
| |
| void set currentResult(Result result) { |
| if (_currentResult != result) { |
| if (_currentResult != null) { |
| _currentResult.row.classes.remove('drop-down-link-select'); |
| } |
| _currentResult = result; |
| if (_currentResult != null) { |
| _currentResult.row.classes.add('drop-down-link-select'); |
| } |
| } |
| } |
| |
| Result get currentResult => _currentResult; |
| |
| /** |
| * Navigate the search drop down using up/down inside the search field. Follow |
| * the result link on enter. |
| */ |
| void handleUpDown(KeyboardEvent event) { |
| if (event.keyIdentifier == KeyName.UP) { |
| currentResultIndex--; |
| event.preventDefault(); |
| } else if (event.keyIdentifier == KeyName.DOWN) { |
| currentResultIndex++; |
| event.preventDefault(); |
| } else if (event.keyIdentifier == KeyName.ENTER) { |
| if (currentResult != null) { |
| window.location.href = currentResult.url; |
| event.preventDefault(); |
| hideDropDown(); |
| } |
| } |
| } |
| |
| /** Show the search drop down unless there are no current results. */ |
| void showDropDown() { |
| if (currentResults.isEmpty) { |
| hideDropDown(); |
| } else { |
| dropdown.style.visibility = 'visible'; |
| } |
| } |
| |
| /** Used to prevent hiding the drop down when it is clicked. */ |
| bool hideDropDownSuspend = false; |
| |
| /** Hide the search drop down unless suspended. */ |
| void hideDropDown() { |
| if (hideDropDownSuspend) return; |
| |
| dropdown.style.visibility = 'hidden'; |
| } |
| |
| /** Activate search on Ctrl+3 and S. */ |
| void shortcutHandler(KeyboardEvent event) { |
| if (event.keyCode == 0x33/* 3 */ && event.ctrlKey) { |
| searchInput.focus(); |
| event.preventDefault(); |
| } else if (event.target != searchInput && event.keyCode == 0x53/* S */) { |
| // Allow writing 's' in the search input. |
| searchInput.focus(); |
| event.preventDefault(); |
| } |
| } |
| |
| /** |
| * Setup window shortcuts. |
| */ |
| void setupShortcuts() { |
| window.on.keyDown.add(shortcutHandler); |
| } |
| |
| /** Setup search hooks. */ |
| void setupSearch(var libraries) { |
| libraryList = libraries; |
| searchInput = query('#q'); |
| dropdown = query('#drop-down'); |
| |
| searchInput.on.keyDown.add(handleUpDown); |
| searchInput.on.keyUp.add(updateDropDown); |
| searchInput.on.change.add(updateDropDown); |
| searchInput.on.reset.add(updateDropDown); |
| searchInput.on.focus.add((event) => showDropDown()); |
| searchInput.on.blur.add((event) => hideDropDown()); |
| } |