Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(862)

Unified Diff: pkg/dartdoc/search.dart

Issue 10829361: 'Find-as-you-type'-search in dartdoc/apidoc. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Rebased Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/dartdoc/nav.dart ('k') | pkg/dartdoc/static/styles.css » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/dartdoc/search.dart
diff --git a/pkg/dartdoc/search.dart b/pkg/dartdoc/search.dart
new file mode 100644
index 0000000000000000000000000000000000000000..342ce6c807e88d5844d2973f1b32ca3ee42d0689
--- /dev/null
+++ b/pkg/dartdoc/search.dart
@@ -0,0 +1,218 @@
+// 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.
+
+/**
+ * [SearchText] represent the search field text. The text is viewed in three
+ * ways: [text] holds the original search text, used for performing
+ * case-sensitive matches, [lowerCase] holds the lower-case search text, used
+ * for performing case-insenstive matches, [camelCase] holds a camel-case
+ * interpretation of the search text, used to order matches in camel-case.
+ */
+class SearchText {
+ final String text;
+ final String lowerCase;
+ final String camelCase;
+
+ SearchText(String searchText)
+ : text = searchText,
+ lowerCase = searchText.toLowerCase(),
+ camelCase = searchText.isEmpty() ? ''
+ : '${searchText.substring(0, 1).toUpperCase()}'
+ '${searchText.substring(1)}';
+
+ int get length() => text.length;
+
+ bool isEmpty() => length == 0;
+}
+
+/**
+ * [StringMatch] represents the case-insensitive matching of [searchText] as a
+ * substring within a [text].
+ */
+class StringMatch {
+ final SearchText searchText;
+ final String text;
+ final int matchOffset;
+ final int matchEnd;
+
+ StringMatch(this.searchText,
+ this.text, this.matchOffset, this.matchEnd);
+
+ /**
+ * Returns the HTML representation of the match.
+ */
+ String toHtml() {
+ return '${text.substring(0, matchOffset)}'
+ '<span class="drop-down-link-highlight">$matchText</span>'
+ '${text.substring(matchEnd)}';
+ }
+
+ String get matchText() =>
+ text.substring(matchOffset, matchEnd);
+
+ /**
+ * Is [:true:] iff [searchText] matches the full [text] case-sensitively.
+ */
+ bool get isFullMatch() => text == searchText.text;
+
+ /**
+ * Is [:true:] iff [searchText] matches a substring of [text]
+ * case-sensitively.
+ */
+ bool get isExactMatch() => matchText == searchText.text;
+
+ /**
+ * Is [:true:] iff [searchText] matches a substring of [text] when
+ * [searchText] is interpreted as camel case.
+ */
+ bool get isCamelCaseMatch() => matchText == searchText.camelCase;
+}
+
+/**
+ * [Result] represents a match of the search text on a library, type or member.
+ */
+class Result {
+ final StringMatch prefix;
+ final StringMatch match;
+
+ final String library;
+ final String type;
+ final String args;
+ final String kind;
+ final String url;
+
+ TableRowElement row;
+
+ Result(this.match, this.kind, this.url,
+ [this.library, this.type, String args, this.prefix])
+ : this.args = args != null ? '&lt;$args&gt;' : '';
+
+ bool get isTopLevel() => prefix == null && type == null;
+
+ void addRow(TableElement table) {
+ if (row != null) return;
+
+ clickHandler(Event event) {
+ window.location.href = url;
+ hideDropDown();
+ }
+
+ row = table.insertRow(table.rows.length);
+ row.classes.add('drop-down-link-tr');
+ row.on.mouseDown.add((event) => hideDropDownSuspend = true);
+ row.on.click.add(clickHandler);
+ row.on.mouseUp.add((event) => hideDropDownSuspend = false);
+ var sb = new StringBuffer();
+ sb.add('<td class="drop-down-link-td">');
+ sb.add('<table class="drop-down-table"><tr><td colspan="2">');
+ if (kind == GETTER) {
+ sb.add('get ');
+ } else if (kind == SETTER) {
+ sb.add('set ');
+ }
+ sb.add(match.toHtml());
+ if (kind == CLASS || kind == INTERFACE || kind == TYPEDEF) {
+ sb.add(args);
+ } else if (kind == CONSTRUCTOR || kind == METHOD) {
+ sb.add('(...)');
+ }
+ sb.add('</td></tr><tr><td class="drop-down-link-kind">');
+ sb.add(kindToString(kind));
+ if (prefix != null) {
+ sb.add(' in ');
+ sb.add(prefix.toHtml());
+ sb.add(args);
+ } else if (type != null) {
+ sb.add(' in ');
+ sb.add(type);
+ sb.add(args);
+ }
+
+ sb.add('</td><td class="drop-down-link-library">');
+ if (library != null) {
+ sb.add('library $library');
+ }
+ sb.add('</td></tr></table></td>');
+ row.innerHTML = sb.toString();
+ }
+}
+
+/**
+ * Creates a [StringMatch] object for [text] if a substring matches
+ * [searchText], or returns [: null :] if no match is found.
+ */
+StringMatch obtainMatch(SearchText searchText, String text) {
+ if (searchText.isEmpty()) {
+ return new StringMatch(searchText, text, 0, 0);
+ }
+ int offset = text.toLowerCase().indexOf(searchText.lowerCase);
+ if (offset != -1) {
+ return new StringMatch(searchText, text,
+ offset, offset + searchText.length);
+ }
+ return null;
+}
+
+/**
+ * Compares [a] and [b], regarding [:true:] smaller than [:false:].
+ *
+ * [:null:]-values are not handled.
+ */
+int compareBools(bool a, bool b) {
+ if (a == b) return 0;
+ return a ? -1 : 1;
+}
+
+/**
+ * Used to sort the search results heuristically to show the more relevant match
+ * in the top of the dropdown.
+ */
+int resultComparator(Result a, Result b) {
+ // Favor top level entities.
+ int result = compareBools(a.isTopLevel, b.isTopLevel);
+ if (result != 0) return result;
+
+ if (a.prefix != null && b.prefix != null) {
+ // Favor full prefix matches.
+ result = compareBools(a.prefix.isFullMatch, b.prefix.isFullMatch);
+ if (result != 0) return result;
+ }
+
+ // Favor matches in the start.
+ result = compareBools(a.match.matchOffset == 0,
+ b.match.matchOffset == 0);
+ if (result != 0) return result;
+
+ // Favor matches to the end. For example, prefer 'cancel' over 'cancelable'
+ result = compareBools(a.match.matchEnd == a.match.text.length,
+ b.match.matchEnd == b.match.text.length);
+ if (result != 0) return result;
+
+ // Favor exact case-sensitive matches.
+ result = compareBools(a.match.isExactMatch, b.match.isExactMatch);
+ if (result != 0) return result;
+
+ // Favor matches that do not break camel-case.
+ result = compareBools(a.match.isCamelCaseMatch, b.match.isCamelCaseMatch);
+ if (result != 0) return result;
+
+ // Favor matches close to the begining.
+ result = a.match.matchOffset.compareTo(b.match.matchOffset);
+ if (result != 0) return result;
+
+ if (a.type != null && b.type != null) {
+ // Favor short type names over long.
+ result = a.type.length.compareTo(b.type.length);
+ if (result != 0) return result;
+
+ // Sort type alphabetically.
+ // TODO(4805): Use [:type.compareToIgnoreCase] when supported.
+ result = a.type.toLowerCase().compareTo(b.type.toLowerCase());
+ if (result != 0) return result;
+ }
+
+ // Sort match alphabetically.
+ // TODO(4805): Use [:text.compareToIgnoreCase] when supported.
+ return a.match.text.toLowerCase().compareTo(b.match.text.toLowerCase());
+}
« no previous file with comments | « pkg/dartdoc/nav.dart ('k') | pkg/dartdoc/static/styles.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698