blob: 0875d52b729a6bda26ded685bf05032e562d923d [file] [log] [blame]
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
* SPDX-License-Identifier: Apache-2.0
*/
const DB_FILE = 'kconfig.json';
const RESULTS_PER_PAGE_OPTIONS = [10, 25, 50];
/* search state */
let db;
let searchOffset;
let maxResults = RESULTS_PER_PAGE_OPTIONS[0];
/* elements */
let input;
let searchTools;
let summaryText;
let results;
let navigation;
let navigationPagesText;
let navigationPrev;
let navigationNext;
/**
* Show an error message.
* @param {String} message Error message.
*/
function showError(message) {
const admonition = document.createElement('div');
admonition.className = 'admonition error';
results.replaceChildren(admonition);
const admonitionTitle = document.createElement('p');
admonitionTitle.className = 'admonition-title';
admonition.appendChild(admonitionTitle);
const admonitionTitleText = document.createTextNode('Error');
admonitionTitle.appendChild(admonitionTitleText);
const admonitionContent = document.createElement('p');
admonition.appendChild(admonitionContent);
const admonitionContentText = document.createTextNode(message);
admonitionContent.appendChild(admonitionContentText);
}
/**
* Show a progress message.
* @param {String} message Progress message.
*/
function showProgress(message) {
const p = document.createElement('p');
p.className = 'centered';
results.replaceChildren(p);
const pText = document.createTextNode(message);
p.appendChild(pText);
}
/**
* Render a Kconfig literal property.
* @param {Element} parent Parent element.
* @param {String} title Title.
* @param {String} content Content.
*/
function renderKconfigPropLiteral(parent, title, content) {
const term = document.createElement('dt');
parent.appendChild(term);
const termText = document.createTextNode(title);
term.appendChild(termText);
const details = document.createElement('dd');
parent.appendChild(details);
const code = document.createElement('code');
code.className = 'docutils literal';
details.appendChild(code);
const literal = document.createElement('span');
literal.className = 'pre';
code.appendChild(literal);
const literalText = document.createTextNode(content);
literal.appendChild(literalText);
}
/**
* Render a Kconfig list property.
* @param {Element} parent Parent element.
* @param {String} title Title.
* @param {list} elements List of elements.
* @param {boolean} linkElements Whether to link elements (treat each element
* as an unformatted option)
*/
function renderKconfigPropList(parent, title, elements, linkElements) {
if (elements.length === 0) {
return;
}
const term = document.createElement('dt');
parent.appendChild(term);
const termText = document.createTextNode(title);
term.appendChild(termText);
const details = document.createElement('dd');
parent.appendChild(details);
const list = document.createElement('ul');
list.className = 'simple';
details.appendChild(list);
elements.forEach(element => {
const listItem = document.createElement('li');
list.appendChild(listItem);
if (linkElements) {
const link = document.createElement('a');
link.href = '#' + element;
listItem.appendChild(link);
const linkText = document.createTextNode(element);
link.appendChild(linkText);
} else {
/* using HTML since element content is pre-formatted */
listItem.innerHTML = element;
}
});
}
/**
* Render a Kconfig list property.
* @param {Element} parent Parent element.
* @param {list} elements List of elements.
* @returns
*/
function renderKconfigDefaults(parent, defaults, alt_defaults) {
if (defaults.length === 0 && alt_defaults.length === 0) {
return;
}
const term = document.createElement('dt');
parent.appendChild(term);
const termText = document.createTextNode('Defaults');
term.appendChild(termText);
const details = document.createElement('dd');
parent.appendChild(details);
if (defaults.length > 0) {
const list = document.createElement('ul');
list.className = 'simple';
details.appendChild(list);
defaults.forEach(entry => {
const listItem = document.createElement('li');
list.appendChild(listItem);
/* using HTML since default content may be pre-formatted */
listItem.innerHTML = entry;
});
}
if (alt_defaults.length > 0) {
const list = document.createElement('ul');
list.className = 'simple';
list.style.display = 'none';
details.appendChild(list);
alt_defaults.forEach(entry => {
const listItem = document.createElement('li');
list.appendChild(listItem);
/* using HTML since default content may be pre-formatted */
listItem.innerHTML = `
${entry[0]}
<em>at</em>
<code class="docutils literal">
<span class"pre">${entry[1]}</span>
</code>`;
});
const show = document.createElement('a');
show.onclick = () => {
if (list.style.display === 'none') {
list.style.display = 'block';
} else {
list.style.display = 'none';
}
};
details.appendChild(show);
const showText = document.createTextNode('Show/Hide other defaults');
show.appendChild(showText);
}
}
/**
* Render a Kconfig entry.
* @param {Object} entry Kconfig entry.
*/
function renderKconfigEntry(entry) {
const container = document.createElement('dl');
container.className = 'kconfig';
/* title (name and permalink) */
const title = document.createElement('dt');
title.className = 'sig sig-object';
container.appendChild(title);
const name = document.createElement('span');
name.className = 'pre';
title.appendChild(name);
const nameText = document.createTextNode(entry.name);
name.appendChild(nameText);
const permalink = document.createElement('a');
permalink.className = 'headerlink';
permalink.href = '#' + entry.name;
title.appendChild(permalink);
const permalinkText = document.createTextNode('\uf0c1');
permalink.appendChild(permalinkText);
/* details */
const details = document.createElement('dd');
container.append(details);
/* prompt and help */
const prompt = document.createElement('p');
details.appendChild(prompt);
const promptTitle = document.createElement('em');
prompt.appendChild(promptTitle);
const promptTitleText = document.createTextNode('');
promptTitle.appendChild(promptTitleText);
if (entry.prompt) {
promptTitleText.nodeValue = entry.prompt;
} else {
promptTitleText.nodeValue = 'No prompt - not directly user assignable.';
}
if (entry.help) {
const help = document.createElement('p');
details.appendChild(help);
const helpText = document.createTextNode(entry.help);
help.appendChild(helpText);
}
/* symbol properties (defaults, selects, etc.) */
const props = document.createElement('dl');
props.className = 'field-list simple';
details.appendChild(props);
renderKconfigPropLiteral(props, 'Type', entry.type);
if (entry.dependencies) {
renderKconfigPropList(props, 'Dependencies', [entry.dependencies]);
}
renderKconfigDefaults(props, entry.defaults, entry.alt_defaults);
renderKconfigPropList(props, 'Selects', entry.selects, false);
renderKconfigPropList(props, 'Selected by', entry.selected_by, true);
renderKconfigPropList(props, 'Implies', entry.implies, false);
renderKconfigPropList(props, 'Implied by', entry.implied_by, true);
renderKconfigPropList(props, 'Ranges', entry.ranges, false);
renderKconfigPropList(props, 'Choices', entry.choices, false);
renderKconfigPropLiteral(props, 'Location', `${entry.filename}:${entry.linenr}`);
renderKconfigPropLiteral(props, 'Menu path', entry.menupath);
return container;
}
/** Perform a search and display the results. */
function doSearch() {
/* replace current state (to handle back button) */
history.replaceState({
value: input.value,
searchOffset: searchOffset
}, '', window.location);
/* nothing to search for */
if (!input.value) {
results.replaceChildren();
navigation.style.visibility = 'hidden';
searchTools.style.visibility = 'hidden';
return;
}
/* perform search */
const regexes = input.value.trim().split(/\s+/).map(
element => new RegExp(element.toLowerCase())
);
let count = 0;
const searchResults = db.filter(entry => {
let matches = 0;
const name = entry.name.toLowerCase();
const prompt = entry.prompt ? entry.prompt.toLowerCase() : "";
regexes.forEach(regex => {
if (name.search(regex) >= 0 || prompt.search(regex) >= 0) {
matches++;
}
});
if (matches === regexes.length) {
count++;
if (count > searchOffset && count <= (searchOffset + maxResults)) {
return true;
}
}
return false;
});
/* show results count and search tools */
summaryText.nodeValue = `${count} options match your search.`;
searchTools.style.visibility = 'visible';
/* update navigation */
navigation.style.visibility = 'visible';
navigationPrev.disabled = searchOffset - maxResults < 0;
navigationNext.disabled = searchOffset + maxResults > count;
const currentPage = Math.floor(searchOffset / maxResults) + 1;
const totalPages = Math.floor(count / maxResults) + 1;
navigationPagesText.nodeValue = `Page ${currentPage} of ${totalPages}`;
/* render Kconfig entries */
results.replaceChildren();
searchResults.forEach(entry => {
results.appendChild(renderKconfigEntry(entry));
});
}
/** Do a search from URL hash */
function doSearchFromURL() {
const rawOption = window.location.hash.substring(1);
if (!rawOption) {
return;
}
const option = rawOption.replace(/[^A-Za-z0-9_]+/g, '');
input.value = '^' + option + '$';
searchOffset = 0;
doSearch();
}
function setupKconfigSearch() {
/* populate kconfig-search container */
const container = document.getElementById('__kconfig-search');
if (!container) {
console.error("Couldn't find Kconfig search container");
return;
}
/* create input field */
input = document.createElement('input');
input.placeholder = 'Type a Kconfig option name (RegEx allowed)';
input.type = 'text';
container.appendChild(input);
/* create search tools container */
searchTools = document.createElement('div');
searchTools.className = 'search-tools';
searchTools.style.visibility = 'hidden';
container.appendChild(searchTools);
/* create search summary */
const searchSummaryContainer = document.createElement('div');
searchTools.appendChild(searchSummaryContainer);
const searchSummary = document.createElement('p');
searchSummaryContainer.appendChild(searchSummary);
summaryText = document.createTextNode('');
searchSummary.appendChild(summaryText);
/* create results per page selector */
const resultsPerPageContainer = document.createElement('div');
resultsPerPageContainer.className = 'results-per-page-container';
searchTools.appendChild(resultsPerPageContainer);
const resultsPerPageTitle = document.createElement('span');
resultsPerPageTitle.className = 'results-per-page-title';
resultsPerPageContainer.appendChild(resultsPerPageTitle);
const resultsPerPageTitleText = document.createTextNode('Results per page:');
resultsPerPageTitle.appendChild(resultsPerPageTitleText);
const resultsPerPageSelect = document.createElement('select');
resultsPerPageSelect.onchange = (event) => {
maxResults = parseInt(event.target.value);
searchOffset = 0;
doSearch();
}
resultsPerPageContainer.appendChild(resultsPerPageSelect);
RESULTS_PER_PAGE_OPTIONS.forEach((value, index) => {
const option = document.createElement('option');
option.value = value;
option.text = value;
option.selected = index === 0;
resultsPerPageSelect.appendChild(option);
});
/* create search results container */
results = document.createElement('div');
container.appendChild(results);
/* create search navigation */
navigation = document.createElement('div');
navigation.className = 'search-nav';
navigation.style.visibility = 'hidden';
container.appendChild(navigation);
navigationPrev = document.createElement('button');
navigationPrev.className = 'btn';
navigationPrev.disabled = true;
navigationPrev.onclick = () => {
searchOffset -= maxResults;
doSearch();
window.scroll(0, 0);
}
navigation.appendChild(navigationPrev);
const navigationPrevText = document.createTextNode('Previous');
navigationPrev.appendChild(navigationPrevText);
const navigationPages = document.createElement('p');
navigation.appendChild(navigationPages);
navigationPagesText = document.createTextNode('');
navigationPages.appendChild(navigationPagesText);
navigationNext = document.createElement('button');
navigationNext.className = 'btn';
navigationNext.disabled = true;
navigationNext.onclick = () => {
searchOffset += maxResults;
doSearch();
window.scroll(0, 0);
}
navigation.appendChild(navigationNext);
const navigationNextText = document.createTextNode('Next');
navigationNext.appendChild(navigationNextText);
/* load database */
showProgress('Loading database...');
fetch(DB_FILE)
.then(response => response.json())
.then(json => {
db = json;
results.replaceChildren();
/* perform initial search */
doSearchFromURL();
/* install event listeners */
input.addEventListener('keyup', () => {
searchOffset = 0;
doSearch();
});
/* install hash change listener (for links) */
window.addEventListener('hashchange', doSearchFromURL);
/* handle back/forward navigation */
window.addEventListener('popstate', (event) => {
if (!event.state) {
return;
}
input.value = event.state.value;
searchOffset = event.state.searchOffset;
doSearch();
});
})
.catch(error => {
showError(`Kconfig database could not be loaded (${error})`);
});
}
setupKconfigSearch();