blob: 18a2320bf615ab036a2087573c4964c3ef31f9a2 [file] [log] [blame]
// 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());
}