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 |