/* * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ */ "use strict"; const messages = { enterTerm: "Enter a search term", noResult: "No results found", oneResult: "Found one result", manyResults: "Found {0} results", loading: "Loading search index...", searching: "Searching...", redirecting: "Redirecting to first result...", } const categories = { modules: "Modules", packages: "Packages", types: "Classes and Interfaces", members: "Members", searchTags: "Search Tags" }; // Localized element descriptors must match values in enum IndexItem.Kind. const itemDesc = [ // Members ["Enum constant in {0}"], ["Variable in {0}"], ["Static variable in {0}"], ["Constructor for {0}"], ["Element in {0}"], ["Method in {0}"], ["Static method in {0}"], ["Record component of {0}"], // Types in upper and lower case ["Annotation Interface", "annotation interface"], ["Enum Class", "enum class"], ["Interface", "interface"], ["Record Class", "record class"], ["Class", "class"], ["Exception Class", "exception class"], // Tags ["Search tag in {0}"], ["System property in {0}"], ["Section in {0}"], ["External specification in {0}"], // Other ["Summary Page"], ]; const mbrDesc = "Member"; const clsDesc = "Class" const pkgDesc = "Package"; const mdlDesc = "Module"; const pkgDescLower = "package"; const mdlDescLower = "module"; const tagDesc = "Search Tag"; const inDesc = "{0} in {1}"; const descDesc = "Description"; const linkLabel = "Go to search page"; const NO_MATCH = {}; const MAX_RESULTS = 300; const UNICODE_LETTER = 0; const UNICODE_DIGIT = 1; const UNICODE_OTHER = 2; function checkUnnamed(name, separator) { return name === "" || !name ? "" : name + separator; } function escapeHtml(str) { return str.replace(//g, ">"); } function getHighlightedText(str, boundaries, from, to) { var start = from; var text = ""; for (var i = 0; i < boundaries.length; i += 2) { var b0 = boundaries[i]; var b1 = boundaries[i + 1]; if (b0 >= to || b1 <= from) { continue; } text += escapeHtml(str.slice(start, Math.max(start, b0))); text += ""; text += escapeHtml(str.slice(Math.max(start, b0), Math.min(to, b1))); text += ""; start = Math.min(to, b1); } text += escapeHtml(str.slice(start, to)); return text; } function getURLPrefix(item, category) { var urlPrefix = ""; var slash = "/"; if (category === "modules") { return item.l + slash; } else if (category === "packages" && item.m) { return item.m + slash; } else if (category === "types" || category === "members") { if (item.m) { urlPrefix = item.m + slash; } else { $.each(packageSearchIndex, function(index, it) { if (it.m && item.p === it.l) { urlPrefix = it.m + slash; item.m = it.m; return false; } }); } } return urlPrefix; } function getURL(item, category) { if (item.url) { return item.url; } var url = getURLPrefix(item, category); if (category === "modules") { url += "module-summary.html"; } else if (category === "packages") { if (item.u) { url = item.u; } else { url += item.l.replace(/\./g, '/') + "/package-summary.html"; } } else if (category === "types") { if (item.u) { url = item.u; } else { url += checkUnnamed(item.p, "/").replace(/\./g, '/') + item.l + ".html"; } } else if (category === "members") { url += checkUnnamed(item.p, "/").replace(/\./g, '/') + item.c + ".html" + "#"; if (item.u) { url += item.u; } else { url += item.l; } } else if (category === "searchTags") { url += item.u; } item.url = url; return url; } function createMatcher(term, camelCase) { if (camelCase && !isUpperCase(term)) { return null; // no need for camel-case matcher for lower case query } var pattern = ""; var upperCase = []; term.trim().split(/\s+/).forEach(function(w, index, array) { var tokens = w.split(/(?=[\p{Lu},.()<>?[\/])/u); for (var i = 0; i < tokens.length; i++) { var s = tokens[i]; // ',' and '?' are the only delimiters commonly followed by space in java signatures pattern += "(" + escapeUnicodeRegex(s).replace(/[,?]/g, "$&\\s*?") + ")"; upperCase.push(false); if (i === tokens.length - 1 && index < array.length - 1) { // space in query string matches all delimiters pattern += "(.*?)"; upperCase.push(isUpperCase(s[0])); } else { if (!camelCase && isUpperCase(s) && s.length === 1) { pattern += "()"; } else { pattern += "([\\p{L}\\p{Nd}\\p{Sc}<>?[\\]]*?)"; } upperCase.push(isUpperCase(s[0])); } } }); var re = new RegExp(pattern, camelCase ? "gu" : "gui"); re.upperCase = upperCase; return re; } // Unicode regular expressions do not allow certain characters to be escaped function escapeUnicodeRegex(pattern) { return pattern.replace(/[\[\]{}()*+?.\\^$|\s]/g, '\\$&'); } function findMatch(matcher, input, startOfName, endOfName, prefixLength) { var from = startOfName; matcher.lastIndex = from; var match = matcher.exec(input); // Expand search area until we get a valid result or reach the beginning of the string while (!match || match.index + match[0].length < startOfName || endOfName < match.index) { if (from === 0) { return NO_MATCH; } from = input.lastIndexOf(".", from - 2) + 1; matcher.lastIndex = from; match = matcher.exec(input); } var boundaries = []; var matchEnd = match.index + match[0].length; var score = 5; var start = match.index; var prevEnd = -1; for (var i = 1; i < match.length; i += 2) { var charType = getCharType(input[start]); // capturing groups come in pairs, match and non-match boundaries.push(start, start + match[i].length); var prevChar = input[start - 1] || ""; var nextChar = input[start + 1] || ""; // make sure group is anchored on a word boundary if (start !== 0 && start !== startOfName) { if (charType === UNICODE_DIGIT && getCharType(prevChar) === UNICODE_DIGIT) { return NO_MATCH; // Numeric token must match at first digit } else if (charType === UNICODE_LETTER && getCharType(prevChar) === UNICODE_LETTER) { if (!isUpperCase(input[start]) || (!isLowerCase(prevChar) && !isLowerCase(nextChar))) { // Not returning NO_MATCH below is to enable upper-case query strings if (!matcher.upperCase[i] || start !== prevEnd) { return NO_MATCH; } else if (!isUpperCase(input[start])) { score -= 1.0; } } } } prevEnd = start + match[i].length; start += match[i].length + match[i + 1].length; // Lower score for unmatched parts between matches if (match[i + 1]) { score -= rateDistance(match[i + 1]); } } // Lower score for unmatched leading part of name if (startOfName < match.index) { score -= rateDistance(input.substring(startOfName, match.index)); } // Favor child or parent variety depending on whether parent is included in search var matchIncludesContaining = match.index < startOfName; // Lower score for unmatched trailing part of name, but exclude member listings if (matchEnd < endOfName && input[matchEnd - 1] !== ".") { let factor = matchIncludesContaining ? 0.1 : 0.8; score -= rateDistance(input.substring(matchEnd, endOfName)) * factor; } // Lower score for unmatched prefix in member class name if (prefixLength < match.index && prefixLength < startOfName) { let factor = matchIncludesContaining ? 0.8 : 0.4; score -= rateDistance(input.substring(prefixLength, Math.min(match.index, startOfName))) * factor; } // Rank qualified names by package name if (prefixLength > 0) { score -= rateDistance(input.substring(0, prefixLength)) * 0.2; } // Reduce score of constructors in member listings if (matchEnd === prefixLength) { score -= 0.1; } return score > 0 ? { input: input, score: score, boundaries: boundaries } : NO_MATCH; } function isLetter(s) { return /\p{L}/u.test(s); } function isUpperCase(s) { return /\p{Lu}/u.test(s); } function isLowerCase(s) { return /\p{Ll}/u.test(s); } function isDigit(s) { return /\p{Nd}/u.test(s); } function getCharType(s) { if (isLetter(s)) { return UNICODE_LETTER; } else if (isDigit(s)) { return UNICODE_DIGIT; } else { return UNICODE_OTHER; } } function rateDistance(str) { // Rate distance of string by counting word boundaries and camel-case tokens return !str ? 0 : (str.split(/\b|(?<=[\p{Ll}_])\p{Lu}/u).length * 0.1 + (isUpperCase(str[0]) ? 0.08 : 0)); } function doSearch(request, response) { var term = request.term.trim(); var maxResults = request.maxResults || MAX_RESULTS; var module = checkUnnamed(request.module, "/"); var matcher = { plainMatcher: createMatcher(term, false), camelCaseMatcher: createMatcher(term, true) } var indexLoaded = indexFilesLoaded(); function getPrefix(item, category) { switch (category) { case "packages": return checkUnnamed(item.m, "/"); case "types": case "members": return checkUnnamed(item.p, "."); default: return ""; } } function getClassPrefix(item, category) { if (category === "members" && (!item.k || (item.k < 8 && item.k !== "3"))) { return item.c + "."; } return ""; } function searchIndex(indexArray, category) { var matches = []; if (!indexArray) { if (!indexLoaded) { matches.push({ l: messages.loading, category: category }); } return matches; } $.each(indexArray, function (i, item) { if (module) { var modulePrefix = getURLPrefix(item, category) || item.u; if (modulePrefix.indexOf("/") > -1 && !modulePrefix.startsWith(module)) { return; } } var prefix = getPrefix(item, category); var classPrefix = getClassPrefix(item, category); var simpleName = classPrefix + item.l; if (item.d) { simpleName += " - " + item.d; } var qualName = prefix + simpleName; var startOfName = classPrefix.length + prefix.length; var endOfName = category === "members" && qualName.indexOf("(", startOfName) > -1 ? qualName.indexOf("(", startOfName) : qualName.length; var m = findMatch(matcher.plainMatcher, qualName, startOfName, endOfName, prefix.length); if (m === NO_MATCH && matcher.camelCaseMatcher) { m = findMatch(matcher.camelCaseMatcher, qualName, startOfName, endOfName, prefix.length); } if (m !== NO_MATCH) { m.indexItem = item; m.name = simpleName; m.category = category; if (m.boundaries[0] < prefix.length) { m.name = qualName; } else { m.boundaries = m.boundaries.map(function(b) { return b - prefix.length; }); } // m.name = m.name + " " + m.score.toFixed(3); matches.push(m); } return true; }); return matches.sort(function(e1, e2) { return e2.score - e1.score || (category !== "members" ? e1.name.localeCompare(e2.name) : 0); }).slice(0, maxResults); } var result = searchIndex(moduleSearchIndex, "modules") .concat(searchIndex(packageSearchIndex, "packages")) .concat(searchIndex(typeSearchIndex, "types")) .concat(searchIndex(memberSearchIndex, "members")) .concat(searchIndex(tagSearchIndex, "searchTags")); if (!indexLoaded) { updateSearchResults = function() { doSearch(request, response); } } else { updateSearchResults = function() {}; } response(result); } // JQuery search menu implementation $.widget("custom.catcomplete", $.ui.autocomplete, { _create: function() { this._super(); this.widget().menu("option", "items", "> .result-item"); // workaround for search result scrolling this.menu._scrollIntoView = function _scrollIntoView( item ) { var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; if ( this._hasScroll() ) { borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0; paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0; offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; scroll = this.activeMenu.scrollTop(); elementHeight = this.activeMenu.height() - 26; itemHeight = item.outerHeight(); if ( offset < 0 ) { this.activeMenu.scrollTop( scroll + offset ); } else if ( offset + itemHeight > elementHeight ) { this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); } } }; }, _renderMenu: function(ul, items) { var currentCategory = ""; var widget = this; widget.menu.bindings = $(); $.each(items, function(index, item) { if (item.category && item.category !== currentCategory) { ul.append("
  • " + categories[item.category] + "
  • "); currentCategory = item.category; } var li = widget._renderItemData(ul, item); if (item.category) { li.attr("aria-label", categories[item.category] + " : " + item.l); } else { li.attr("aria-label", item.l); } li.attr("class", "result-item"); }); ul.append(""); }, _renderItem: function(ul, item) { var label = getResultLabel(item); var resultDesc = getResultDescription(item); return $("
  • ") .append($("
    ") .append($("").addClass("search-result-label").html(label)) .append($("").addClass("search-result-desc").html(resultDesc))) .appendTo(ul); }, _resizeMenu: function () { var ul = this.menu.element; var missing = 0; ul.children().each((i, e) => { if (e.hasChildNodes() && e.firstChild.hasChildNodes()) { var label = e.firstChild.firstChild; missing = Math.max(missing, label.scrollWidth - label.clientWidth); } }); ul.outerWidth( Math.max( ul.width("").outerWidth() + missing + 40, this.element.outerWidth() )); } }); function getResultLabel(item) { if (item.l) { return item.l; } return getHighlightedText(item.name, item.boundaries, 0, item.name.length); } function getResultDescription(item) { if (!item.indexItem) { return ""; } var kind; switch (item.category) { case "members": var typeName = checkUnnamed(item.indexItem.p, ".") + item.indexItem.c; var typeDesc = getEnclosingTypeDesc(item.indexItem); kind = itemDesc[item.indexItem.k || 5][0]; return kind.replace("{0}", typeDesc + " " + typeName); case "types": var pkgName = checkUnnamed(item.indexItem.p, ""); kind = itemDesc[item.indexItem.k || 12][0]; if (!pkgName) { // Handle "All Classes" summary page and unnamed package return item.indexItem.k === "18" ? kind : kind + " " + item.indexItem.l; } return getEnclosingDescription(kind, pkgDescLower, pkgName); case "packages": if (item.indexItem.k === "18") { return itemDesc[item.indexItem.k][0]; // "All Packages" summary page } else if (!item.indexItem.m) { return pkgDesc + " " + item.indexItem.l; } var mdlName = item.indexItem.m; return getEnclosingDescription(pkgDesc, mdlDescLower, mdlName); case "modules": return mdlDesc + " " + item.indexItem.l; case "searchTags": if (item.indexItem) { var holder = item.indexItem.h; kind = itemDesc[item.indexItem.k || 14][0]; return holder ? kind.replace("{0}", holder) : kind; } } return ""; } function getEnclosingDescription(elem, desc, label) { return inDesc.replace("{0}", elem).replace("{1}", desc + " " + label); } function getEnclosingTypeDesc(item) { if (!item.typeDesc) { $.each(typeSearchIndex, function(index, it) { if (it.l === item.c && it.p === item.p && it.m === item.m) { item.typeDesc = itemDesc[it.k || 12][1]; return false; } }); } return item.typeDesc || ""; } $(function() { var search = $("#search-input"); var reset = $("#reset-search"); search.catcomplete({ minLength: 1, delay: 200, source: function(request, response) { if (request.term.trim() === "") { return this.close(); } // Prevent selection of item at current mouse position this.menu.previousFilter = "_"; this.menu.filterTimer = this.menu._delay(function() { delete this.previousFilter; }, 1000); return doSearch(request, response); }, response: function(event, ui) { if (!ui.content.length) { ui.content.push({ l: messages.noResult }); } }, autoFocus: true, focus: function(event, ui) { return false; }, position: { collision: "flip" }, select: function(event, ui) { if (ui.item.indexItem) { var url = getURL(ui.item.indexItem, ui.item.category); window.location.href = pathtoroot + url; search.blur(); } } }); search.val(''); search.on("input", () => reset.css("visibility", search.val() ? "visible" : "hidden")) search.prop("disabled", false); search.attr("autocapitalize", "off"); reset.prop("disabled", false); reset.click(function() { search.val('').focus(); }); });