OSDN Git Service

cherry-pick from master: 42092024ebd81896e741829c50f37a7e0095392e
authorRoman Nurik <romannurik@google.com>
Tue, 13 Apr 2010 23:23:42 +0000 (16:23 -0700)
committerScott Main <smain@google.com>
Mon, 19 Apr 2010 22:18:41 +0000 (15:18 -0700)
Enable query highlighting and result ranking for search autocomplete on d.a.c. Also make it case-insensitive.

Change-Id: I3dd0e4edd7efae5a5758952699c08f1a46bdfeda

tools/droiddoc/templates/assets/android-developer-core.css
tools/droiddoc/templates/assets/search_autocomplete.js

index 750567e..92e4c53 100644 (file)
@@ -519,7 +519,7 @@ div.indent {
   padding-right: 6px;
   padding-top: 1px;
   padding-bottom: 1px;
-  font-size: .8em;
+  font-size: 0.81em;
   border: none;
   margin: 0;
   line-height: 1.05em;
index 4fa47a5..dd4552f 100644 (file)
@@ -2,7 +2,7 @@ var gSelectedIndex = -1;
 var gSelectedID = -1;
 var gMatches = new Array();
 var gLastText = "";
-var ROW_COUNT = 30;
+var ROW_COUNT = 20;
 var gInitialized = false;
 var DEFAULT_TEXT = "search developer docs";
 
@@ -22,7 +22,7 @@ function set_row_selected(row, selected)
 function set_row_values(toroot, row, match)
 {
     var link = row.cells[0].childNodes[0];
-    link.innerHTML = match.label;
+    link.innerHTML = match.__hilabel || match.label;
     link.href = toroot + match.link
   //  row.cells[1].innerHTML = match.type;
 }
@@ -104,7 +104,7 @@ function sync_selection_table(toroot)
 function search_changed(e, kd, toroot)
 {
     var search = document.getElementById("search_autocomplete");
-    var text = search.value;
+    var text = search.value.replace(/(^ +)|( +$)/g, '');
 
     // 13 = enter
     if (e.keyCode == 13) {
@@ -137,21 +137,112 @@ function search_changed(e, kd, toroot)
         gMatches = new Array();
         matchedCount = 0;
         gSelectedIndex = -1;
-        for (i=0; i<DATA.length; i++) {
+        for (var i=0; i<DATA.length; i++) {
             var s = DATA[i];
-            if (text.length != 0 && s.label.indexOf(text) != -1) {
+            if (text.length != 0 &&
+                  s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
                 gMatches[matchedCount] = s;
-                if (gSelectedID == s.id) {
-                    gSelectedIndex = matchedCount;
-                }
                 matchedCount++;
             }
         }
+        rank_autocomplete_results(text);
+        for (var i=0; i<gMatches.length; i++) {
+            var s = gMatches[i];
+            if (gSelectedID == s.id) {
+                gSelectedIndex = i;
+            }
+        }
+        highlight_autocomplete_result_labels(text);
         sync_selection_table(toroot);
         return true; // allow the event to bubble up to the search api
     }
 }
 
+function rank_autocomplete_results(query) {
+    query = query || '';
+    if (!gMatches || !gMatches.length)
+      return;
+
+    // helper function that gets the last occurence index of the given regex
+    // in the given string, or -1 if not found
+    var _lastSearch = function(s, re) {
+      if (s == '')
+        return -1;
+      var l = -1;
+      var tmp;
+      while ((tmp = s.search(re)) >= 0) {
+        if (l < 0) l = 0;
+        l += tmp;
+        s = s.substr(tmp + 1);
+      }
+      return l;
+    };
+
+    // helper function that counts the occurrences of a given character in
+    // a given string
+    var _countChar = function(s, c) {
+      var n = 0;
+      for (var i=0; i<s.length; i++)
+        if (s.charAt(i) == c) ++n;
+      return n;
+    };
+
+    var queryLower = query.toLowerCase();
+    var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
+    var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
+    var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
+
+    var _resultScoreFn = function(result) {
+        // scores are calculated based on exact and prefix matches,
+        // and then number of path separators (dots) from the last
+        // match (i.e. favoring classes and deep package names)
+        var score = 1.0;
+        var labelLower = result.label.toLowerCase();
+        var t;
+        t = _lastSearch(labelLower, partExactAlnumRE);
+        if (t >= 0) {
+            // exact part match
+            var partsAfter = _countChar(labelLower.substr(t + 1), '.');
+            score *= 200 / (partsAfter + 1);
+        } else {
+            t = _lastSearch(labelLower, partPrefixAlnumRE);
+            if (t >= 0) {
+                // part prefix match
+                var partsAfter = _countChar(labelLower.substr(t + 1), '.');
+                score *= 20 / (partsAfter + 1);
+            }
+        }
+
+        return score;
+    };
+
+    for (var i=0; i<gMatches.length; i++) {
+        gMatches[i].__resultScore = _resultScoreFn(gMatches[i]);
+    }
+
+    gMatches.sort(function(a,b){
+        var n = b.__resultScore - a.__resultScore;
+        if (n == 0) // lexicographical sort if scores are the same
+            n = (a.label < b.label) ? -1 : 1;
+        return n;
+    });
+}
+
+function highlight_autocomplete_result_labels(query) {
+    query = query || '';
+    if (!gMatches || !gMatches.length)
+      return;
+
+    var queryLower = query.toLowerCase();
+    var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
+    var queryRE = new RegExp(
+        '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
+    for (var i=0; i<gMatches.length; i++) {
+        gMatches[i].__hilabel = gMatches[i].label.replace(
+            queryRE, '<b>$1</b>');
+    }
+}
+
 function search_focus_changed(obj, focused)
 {
     if (focused) {