uni

Thing1's amazing uni repo
Log | Files | Refs | Submodules

search.js (13759B)


      1 /*
      2  * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
      3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      4  *
      5  * This code is free software; you can redistribute it and/or modify it
      6  * under the terms of the GNU General Public License version 2 only, as
      7  * published by the Free Software Foundation.  Oracle designates this
      8  * particular file as subject to the "Classpath" exception as provided
      9  * by Oracle in the LICENSE file that accompanied this code.
     10  *
     11  * This code is distributed in the hope that it will be useful, but WITHOUT
     12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     14  * version 2 for more details (a copy is included in the LICENSE file that
     15  * accompanied this code).
     16  *
     17  * You should have received a copy of the GNU General Public License version
     18  * 2 along with this work; if not, write to the Free Software Foundation,
     19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     20  *
     21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     22  * or visit www.oracle.com if you need additional information or have any
     23  * questions.
     24  */
     25 
     26 var noResult = {l: "No results found"};
     27 var loading = {l: "Loading search index..."};
     28 var catModules = "Modules";
     29 var catPackages = "Packages";
     30 var catTypes = "Classes and Interfaces";
     31 var catMembers = "Members";
     32 var catSearchTags = "Search Tags";
     33 var highlight = "<span class=\"result-highlight\">$&</span>";
     34 var searchPattern = "";
     35 var fallbackPattern = "";
     36 var RANKING_THRESHOLD = 2;
     37 var NO_MATCH = 0xffff;
     38 var MIN_RESULTS = 3;
     39 var MAX_RESULTS = 500;
     40 var UNNAMED = "<Unnamed>";
     41 function escapeHtml(str) {
     42     return str.replace(/</g, "&lt;").replace(/>/g, "&gt;");
     43 }
     44 function getHighlightedText(item, matcher, fallbackMatcher) {
     45     var escapedItem = escapeHtml(item);
     46     var highlighted = escapedItem.replace(matcher, highlight);
     47     if (highlighted === escapedItem) {
     48         highlighted = escapedItem.replace(fallbackMatcher, highlight)
     49     }
     50     return highlighted;
     51 }
     52 function getURLPrefix(ui) {
     53     var urlPrefix="";
     54     var slash = "/";
     55     if (ui.item.category === catModules) {
     56         return ui.item.l + slash;
     57     } else if (ui.item.category === catPackages && ui.item.m) {
     58         return ui.item.m + slash;
     59     } else if (ui.item.category === catTypes || ui.item.category === catMembers) {
     60         if (ui.item.m) {
     61             urlPrefix = ui.item.m + slash;
     62         } else {
     63             $.each(packageSearchIndex, function(index, item) {
     64                 if (item.m && ui.item.p === item.l) {
     65                     urlPrefix = item.m + slash;
     66                 }
     67             });
     68         }
     69     }
     70     return urlPrefix;
     71 }
     72 function createSearchPattern(term) {
     73     var pattern = "";
     74     var isWordToken = false;
     75     term.replace(/,\s*/g, ", ").trim().split(/\s+/).forEach(function(w, index) {
     76         if (index > 0) {
     77             // whitespace between identifiers is significant
     78             pattern += (isWordToken && /^\w/.test(w)) ? "\\s+" : "\\s*";
     79         }
     80         var tokens = w.split(/(?=[A-Z,.()<>[\/])/);
     81         for (var i = 0; i < tokens.length; i++) {
     82             var s = tokens[i];
     83             if (s === "") {
     84                 continue;
     85             }
     86             pattern += $.ui.autocomplete.escapeRegex(s);
     87             isWordToken =  /\w$/.test(s);
     88             if (isWordToken) {
     89                 pattern += "([a-z0-9_$<>\\[\\]]*?)";
     90             }
     91         }
     92     });
     93     return pattern;
     94 }
     95 function createMatcher(pattern, flags) {
     96     var isCamelCase = /[A-Z]/.test(pattern);
     97     return new RegExp(pattern, flags + (isCamelCase ? "" : "i"));
     98 }
     99 var watermark = 'Search';
    100 $(function() {
    101     var search = $("#search-input");
    102     var reset = $("#reset-button");
    103     search.val('');
    104     search.prop("disabled", false);
    105     reset.prop("disabled", false);
    106     search.val(watermark).addClass('watermark');
    107     search.blur(function() {
    108         if ($(this).val().length === 0) {
    109             $(this).val(watermark).addClass('watermark');
    110         }
    111     });
    112     search.on('click keydown paste', function() {
    113         if ($(this).val() === watermark) {
    114             $(this).val('').removeClass('watermark');
    115         }
    116     });
    117     reset.click(function() {
    118         search.val('').focus();
    119     });
    120     search.focus()[0].setSelectionRange(0, 0);
    121 });
    122 $.widget("custom.catcomplete", $.ui.autocomplete, {
    123     _create: function() {
    124         this._super();
    125         this.widget().menu("option", "items", "> :not(.ui-autocomplete-category)");
    126     },
    127     _renderMenu: function(ul, items) {
    128         var rMenu = this;
    129         var currentCategory = "";
    130         rMenu.menu.bindings = $();
    131         $.each(items, function(index, item) {
    132             var li;
    133             if (item.category && item.category !== currentCategory) {
    134                 ul.append("<li class=\"ui-autocomplete-category\">" + item.category + "</li>");
    135                 currentCategory = item.category;
    136             }
    137             li = rMenu._renderItemData(ul, item);
    138             if (item.category) {
    139                 li.attr("aria-label", item.category + " : " + item.l);
    140                 li.attr("class", "result-item");
    141             } else {
    142                 li.attr("aria-label", item.l);
    143                 li.attr("class", "result-item");
    144             }
    145         });
    146     },
    147     _renderItem: function(ul, item) {
    148         var label = "";
    149         var matcher = createMatcher(escapeHtml(searchPattern), "g");
    150         var fallbackMatcher = new RegExp(fallbackPattern, "gi")
    151         if (item.category === catModules) {
    152             label = getHighlightedText(item.l, matcher, fallbackMatcher);
    153         } else if (item.category === catPackages) {
    154             label = getHighlightedText(item.l, matcher, fallbackMatcher);
    155         } else if (item.category === catTypes) {
    156             label = (item.p && item.p !== UNNAMED)
    157                     ? getHighlightedText(item.p + "." + item.l, matcher, fallbackMatcher)
    158                     : getHighlightedText(item.l, matcher, fallbackMatcher);
    159         } else if (item.category === catMembers) {
    160             label = (item.p && item.p !== UNNAMED)
    161                     ? getHighlightedText(item.p + "." + item.c + "." + item.l, matcher, fallbackMatcher)
    162                     : getHighlightedText(item.c + "." + item.l, matcher, fallbackMatcher);
    163         } else if (item.category === catSearchTags) {
    164             label = getHighlightedText(item.l, matcher, fallbackMatcher);
    165         } else {
    166             label = item.l;
    167         }
    168         var li = $("<li/>").appendTo(ul);
    169         var div = $("<div/>").appendTo(li);
    170         if (item.category === catSearchTags && item.h) {
    171             if (item.d) {
    172                 div.html(label + "<span class=\"search-tag-holder-result\"> (" + item.h + ")</span><br><span class=\"search-tag-desc-result\">"
    173                                 + item.d + "</span><br>");
    174             } else {
    175                 div.html(label + "<span class=\"search-tag-holder-result\"> (" + item.h + ")</span>");
    176             }
    177         } else {
    178             if (item.m) {
    179                 div.html(item.m + "/" + label);
    180             } else {
    181                 div.html(label);
    182             }
    183         }
    184         return li;
    185     }
    186 });
    187 function rankMatch(match, category) {
    188     if (!match) {
    189         return NO_MATCH;
    190     }
    191     var index = match.index;
    192     var input = match.input;
    193     var leftBoundaryMatch = 2;
    194     var periferalMatch = 0;
    195     // make sure match is anchored on a left word boundary
    196     if (index === 0 || /\W/.test(input[index - 1]) || "_" === input[index]) {
    197         leftBoundaryMatch = 0;
    198     } else if ("_" === input[index - 1] || (input[index] === input[index].toUpperCase() && !/^[A-Z0-9_$]+$/.test(input))) {
    199         leftBoundaryMatch = 1;
    200     }
    201     var matchEnd = index + match[0].length;
    202     var leftParen = input.indexOf("(");
    203     var endOfName = leftParen > -1 ? leftParen : input.length;
    204     // exclude peripheral matches
    205     if (category !== catModules && category !== catSearchTags) {
    206         var delim = category === catPackages ? "/" : ".";
    207         if (leftParen > -1 && leftParen < index) {
    208             periferalMatch += 2;
    209         } else if (input.lastIndexOf(delim, endOfName) >= matchEnd) {
    210             periferalMatch += 2;
    211         }
    212     }
    213     var delta = match[0].length === endOfName ? 0 : 1; // rank full match higher than partial match
    214     for (var i = 1; i < match.length; i++) {
    215         // lower ranking if parts of the name are missing
    216         if (match[i])
    217             delta += match[i].length;
    218     }
    219     if (category === catTypes) {
    220         // lower ranking if a type name contains unmatched camel-case parts
    221         if (/[A-Z]/.test(input.substring(matchEnd)))
    222             delta += 5;
    223         if (/[A-Z]/.test(input.substring(0, index)))
    224             delta += 5;
    225     }
    226     return leftBoundaryMatch + periferalMatch + (delta / 200);
    227 
    228 }
    229 function doSearch(request, response) {
    230     var result = [];
    231     searchPattern = createSearchPattern(request.term);
    232     fallbackPattern = createSearchPattern(request.term.toLowerCase());
    233     if (searchPattern === "") {
    234         return this.close();
    235     }
    236     var camelCaseMatcher = createMatcher(searchPattern, "");
    237     var fallbackMatcher = new RegExp(fallbackPattern, "i");
    238 
    239     function searchIndexWithMatcher(indexArray, matcher, category, nameFunc) {
    240         if (indexArray) {
    241             var newResults = [];
    242             $.each(indexArray, function (i, item) {
    243                 item.category = category;
    244                 var ranking = rankMatch(matcher.exec(nameFunc(item)), category);
    245                 if (ranking < RANKING_THRESHOLD) {
    246                     newResults.push({ranking: ranking, item: item});
    247                 }
    248                 return newResults.length <= MAX_RESULTS;
    249             });
    250             return newResults.sort(function(e1, e2) {
    251                 return e1.ranking - e2.ranking;
    252             }).map(function(e) {
    253                 return e.item;
    254             });
    255         }
    256         return [];
    257     }
    258     function searchIndex(indexArray, category, nameFunc) {
    259         var primaryResults = searchIndexWithMatcher(indexArray, camelCaseMatcher, category, nameFunc);
    260         result = result.concat(primaryResults);
    261         if (primaryResults.length <= MIN_RESULTS && !camelCaseMatcher.ignoreCase) {
    262             var secondaryResults = searchIndexWithMatcher(indexArray, fallbackMatcher, category, nameFunc);
    263             result = result.concat(secondaryResults.filter(function (item) {
    264                 return primaryResults.indexOf(item) === -1;
    265             }));
    266         }
    267     }
    268 
    269     searchIndex(moduleSearchIndex, catModules, function(item) { return item.l; });
    270     searchIndex(packageSearchIndex, catPackages, function(item) {
    271         return (item.m && request.term.indexOf("/") > -1)
    272             ? (item.m + "/" + item.l) : item.l;
    273     });
    274     searchIndex(typeSearchIndex, catTypes, function(item) {
    275         return request.term.indexOf(".") > -1 ? item.p + "." + item.l : item.l;
    276     });
    277     searchIndex(memberSearchIndex, catMembers, function(item) {
    278         return request.term.indexOf(".") > -1
    279             ? item.p + "." + item.c + "." + item.l : item.l;
    280     });
    281     searchIndex(tagSearchIndex, catSearchTags, function(item) { return item.l; });
    282 
    283     if (!indexFilesLoaded()) {
    284         updateSearchResults = function() {
    285             doSearch(request, response);
    286         }
    287         result.unshift(loading);
    288     } else {
    289         updateSearchResults = function() {};
    290     }
    291     response(result);
    292 }
    293 $(function() {
    294     $("#search-input").catcomplete({
    295         minLength: 1,
    296         delay: 300,
    297         source: doSearch,
    298         response: function(event, ui) {
    299             if (!ui.content.length) {
    300                 ui.content.push(noResult);
    301             } else {
    302                 $("#search-input").empty();
    303             }
    304         },
    305         autoFocus: true,
    306         focus: function(event, ui) {
    307             return false;
    308         },
    309         position: {
    310             collision: "flip"
    311         },
    312         select: function(event, ui) {
    313             if (ui.item.category) {
    314                 var url = getURLPrefix(ui);
    315                 if (ui.item.category === catModules) {
    316                     url += "module-summary.html";
    317                 } else if (ui.item.category === catPackages) {
    318                     if (ui.item.u) {
    319                         url = ui.item.u;
    320                     } else {
    321                         url += ui.item.l.replace(/\./g, '/') + "/package-summary.html";
    322                     }
    323                 } else if (ui.item.category === catTypes) {
    324                     if (ui.item.u) {
    325                         url = ui.item.u;
    326                     } else if (ui.item.p === UNNAMED) {
    327                         url += ui.item.l + ".html";
    328                     } else {
    329                         url += ui.item.p.replace(/\./g, '/') + "/" + ui.item.l + ".html";
    330                     }
    331                 } else if (ui.item.category === catMembers) {
    332                     if (ui.item.p === UNNAMED) {
    333                         url += ui.item.c + ".html" + "#";
    334                     } else {
    335                         url += ui.item.p.replace(/\./g, '/') + "/" + ui.item.c + ".html" + "#";
    336                     }
    337                     if (ui.item.u) {
    338                         url += ui.item.u;
    339                     } else {
    340                         url += ui.item.l;
    341                     }
    342                 } else if (ui.item.category === catSearchTags) {
    343                     url += ui.item.u;
    344                 }
    345                 if (top !== window) {
    346                     parent.classFrame.location = pathtoroot + url;
    347                 } else {
    348                     window.location.href = pathtoroot + url;
    349                 }
    350                 $("#search-input").focus();
    351             }
    352         }
    353     });
    354 });