| 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 /** | |
| 6 * [SearchText] represent the search field text. The text is viewed in three | |
| 7 * ways: [text] holds the original search text, used for performing | |
| 8 * case-sensitive matches, [lowerCase] holds the lower-case search text, used | |
| 9 * for performing case-insenstive matches, [camelCase] holds a camel-case | |
| 10 * interpretation of the search text, used to order matches in camel-case. | |
| 11 */ | |
| 12 class SearchText { | |
| 13 final String text; | |
| 14 final String lowerCase; | |
| 15 final String camelCase; | |
| 16 | |
| 17 SearchText(String searchText) | |
| 18 : text = searchText, | |
| 19 lowerCase = searchText.toLowerCase(), | |
| 20 camelCase = searchText.isEmpty() ? '' | |
| 21 : '${searchText.substring(0, 1).toUpperCase()}' | |
| 22 '${searchText.substring(1)}'; | |
| 23 | |
| 24 int get length => text.length; | |
| 25 | |
| 26 bool isEmpty() => length == 0; | |
| 27 } | |
| 28 | |
| 29 /** | |
| 30 * [StringMatch] represents the case-insensitive matching of [searchText] as a | |
| 31 * substring within a [text]. | |
| 32 */ | |
| 33 class StringMatch { | |
| 34 final SearchText searchText; | |
| 35 final String text; | |
| 36 final int matchOffset; | |
| 37 final int matchEnd; | |
| 38 | |
| 39 StringMatch(this.searchText, | |
| 40 this.text, this.matchOffset, this.matchEnd); | |
| 41 | |
| 42 /** | |
| 43 * Returns the HTML representation of the match. | |
| 44 */ | |
| 45 String toHtml() { | |
| 46 return '${text.substring(0, matchOffset)}' | |
| 47 '<span class="drop-down-link-highlight">$matchText</span>' | |
| 48 '${text.substring(matchEnd)}'; | |
| 49 } | |
| 50 | |
| 51 String get matchText => | |
| 52 text.substring(matchOffset, matchEnd); | |
| 53 | |
| 54 /** | |
| 55 * Is [:true:] iff [searchText] matches the full [text] case-sensitively. | |
| 56 */ | |
| 57 bool get isFullMatch => text == searchText.text; | |
| 58 | |
| 59 /** | |
| 60 * Is [:true:] iff [searchText] matches a substring of [text] | |
| 61 * case-sensitively. | |
| 62 */ | |
| 63 bool get isExactMatch => matchText == searchText.text; | |
| 64 | |
| 65 /** | |
| 66 * Is [:true:] iff [searchText] matches a substring of [text] when | |
| 67 * [searchText] is interpreted as camel case. | |
| 68 */ | |
| 69 bool get isCamelCaseMatch => matchText == searchText.camelCase; | |
| 70 } | |
| 71 | |
| 72 /** | |
| 73 * [Result] represents a match of the search text on a library, type or member. | |
| 74 */ | |
| 75 class Result { | |
| 76 final StringMatch prefix; | |
| 77 final StringMatch match; | |
| 78 | |
| 79 final String library; | |
| 80 final String type; | |
| 81 final String args; | |
| 82 final String kind; | |
| 83 final String url; | |
| 84 | |
| 85 TableRowElement row; | |
| 86 | |
| 87 Result(this.match, this.kind, this.url, | |
| 88 [this.library, this.type, String args, this.prefix]) | |
| 89 : this.args = args != null ? '<$args>' : ''; | |
| 90 | |
| 91 bool get isTopLevel => prefix == null && type == null; | |
| 92 | |
| 93 void addRow(TableElement table) { | |
| 94 if (row != null) return; | |
| 95 | |
| 96 clickHandler(Event event) { | |
| 97 window.location.href = url; | |
| 98 hideDropDown(); | |
| 99 } | |
| 100 | |
| 101 row = table.insertRow(table.rows.length); | |
| 102 row.classes.add('drop-down-link-tr'); | |
| 103 row.on.mouseDown.add((event) => hideDropDownSuspend = true); | |
| 104 row.on.click.add(clickHandler); | |
| 105 row.on.mouseUp.add((event) => hideDropDownSuspend = false); | |
| 106 var sb = new StringBuffer(); | |
| 107 sb.add('<td class="drop-down-link-td">'); | |
| 108 sb.add('<table class="drop-down-table"><tr><td colspan="2">'); | |
| 109 if (kind == GETTER) { | |
| 110 sb.add('get '); | |
| 111 } else if (kind == SETTER) { | |
| 112 sb.add('set '); | |
| 113 } | |
| 114 sb.add(match.toHtml()); | |
| 115 if (kind == CLASS || kind == INTERFACE || kind == TYPEDEF) { | |
| 116 sb.add(args); | |
| 117 } else if (kind == CONSTRUCTOR || kind == METHOD) { | |
| 118 sb.add('(...)'); | |
| 119 } | |
| 120 sb.add('</td></tr><tr><td class="drop-down-link-kind">'); | |
| 121 sb.add(kindToString(kind)); | |
| 122 if (prefix != null) { | |
| 123 sb.add(' in '); | |
| 124 sb.add(prefix.toHtml()); | |
| 125 sb.add(args); | |
| 126 } else if (type != null) { | |
| 127 sb.add(' in '); | |
| 128 sb.add(type); | |
| 129 sb.add(args); | |
| 130 } | |
| 131 | |
| 132 sb.add('</td><td class="drop-down-link-library">'); | |
| 133 if (library != null) { | |
| 134 sb.add('library $library'); | |
| 135 } | |
| 136 sb.add('</td></tr></table></td>'); | |
| 137 row.innerHTML = sb.toString(); | |
| 138 } | |
| 139 } | |
| 140 | |
| 141 /** | |
| 142 * Creates a [StringMatch] object for [text] if a substring matches | |
| 143 * [searchText], or returns [: null :] if no match is found. | |
| 144 */ | |
| 145 StringMatch obtainMatch(SearchText searchText, String text) { | |
| 146 if (searchText.isEmpty()) { | |
| 147 return new StringMatch(searchText, text, 0, 0); | |
| 148 } | |
| 149 int offset = text.toLowerCase().indexOf(searchText.lowerCase); | |
| 150 if (offset != -1) { | |
| 151 return new StringMatch(searchText, text, | |
| 152 offset, offset + searchText.length); | |
| 153 } | |
| 154 return null; | |
| 155 } | |
| 156 | |
| 157 /** | |
| 158 * Compares [a] and [b], regarding [:true:] smaller than [:false:]. | |
| 159 * | |
| 160 * [:null:]-values are not handled. | |
| 161 */ | |
| 162 int compareBools(bool a, bool b) { | |
| 163 if (a == b) return 0; | |
| 164 return a ? -1 : 1; | |
| 165 } | |
| 166 | |
| 167 /** | |
| 168 * Used to sort the search results heuristically to show the more relevant match | |
| 169 * in the top of the dropdown. | |
| 170 */ | |
| 171 int resultComparator(Result a, Result b) { | |
| 172 // Favor top level entities. | |
| 173 int result = compareBools(a.isTopLevel, b.isTopLevel); | |
| 174 if (result != 0) return result; | |
| 175 | |
| 176 if (a.prefix != null && b.prefix != null) { | |
| 177 // Favor full prefix matches. | |
| 178 result = compareBools(a.prefix.isFullMatch, b.prefix.isFullMatch); | |
| 179 if (result != 0) return result; | |
| 180 } | |
| 181 | |
| 182 // Favor matches in the start. | |
| 183 result = compareBools(a.match.matchOffset == 0, | |
| 184 b.match.matchOffset == 0); | |
| 185 if (result != 0) return result; | |
| 186 | |
| 187 // Favor matches to the end. For example, prefer 'cancel' over 'cancelable' | |
| 188 result = compareBools(a.match.matchEnd == a.match.text.length, | |
| 189 b.match.matchEnd == b.match.text.length); | |
| 190 if (result != 0) return result; | |
| 191 | |
| 192 // Favor exact case-sensitive matches. | |
| 193 result = compareBools(a.match.isExactMatch, b.match.isExactMatch); | |
| 194 if (result != 0) return result; | |
| 195 | |
| 196 // Favor matches that do not break camel-case. | |
| 197 result = compareBools(a.match.isCamelCaseMatch, b.match.isCamelCaseMatch); | |
| 198 if (result != 0) return result; | |
| 199 | |
| 200 // Favor matches close to the begining. | |
| 201 result = a.match.matchOffset.compareTo(b.match.matchOffset); | |
| 202 if (result != 0) return result; | |
| 203 | |
| 204 if (a.type != null && b.type != null) { | |
| 205 // Favor short type names over long. | |
| 206 result = a.type.length.compareTo(b.type.length); | |
| 207 if (result != 0) return result; | |
| 208 | |
| 209 // Sort type alphabetically. | |
| 210 // TODO(4805): Use [:type.compareToIgnoreCase] when supported. | |
| 211 result = a.type.toLowerCase().compareTo(b.type.toLowerCase()); | |
| 212 if (result != 0) return result; | |
| 213 } | |
| 214 | |
| 215 // Sort match alphabetically. | |
| 216 // TODO(4805): Use [:text.compareToIgnoreCase] when supported. | |
| 217 return a.match.text.toLowerCase().compareTo(b.match.text.toLowerCase()); | |
| 218 } | |
| OLD | NEW |