Add RustSearchOmnibox class

This commit is contained in:
Folyd 2024-06-15 14:53:33 -07:00
parent 2cd58ec1d6
commit 68d011e310
2 changed files with 346 additions and 305 deletions

334
extension/lib.js Normal file
View file

@ -0,0 +1,334 @@
import settings from "./settings.js";
import Statistics from "./statistics.js";
import HistoryCommand from "./core/command/history.js";
import CrateDocManager from "./crate-manager.js";
import { Compat } from "./core/index.js";
import {
LINT_URL,
REDIRECT_URL,
} from "./constants.js";
export default class RustSearchOmnibox {
static async run({
omnibox,
stdSearcher,
nightlySearcher,
crateDocSearcher,
crateSearcher,
attributeSearcher,
bookSearcher,
caniuseSearcher,
lintSearcher,
commandManager,
}) {
// All dynamic setting items. Those items will been updated
// in chrome.storage.onchange listener callback.
let isOfflineMode = await settings.isOfflineMode;
let offlineDocPath = await settings.offlineDocPath;
let defaultSearch = await settings.defaultSearch;
let crateRegistry = await settings.crateRegistry;
function formatDoc(index, doc) {
let content = doc.href;
let description = doc.displayPath + `<match>${doc.name}</match>`;
if (doc.desc) {
description += ` - <dim>${Compat.escape(Compat.eliminateTags(doc.desc))}</dim>`;
}
if (doc.queryType === "s" || doc.queryType === "src") {
let url = new URL(doc.href);
url.search = "?mode=src";
content = url.toString();
description = `[Source code] ${description}`;
}
return { content, description };
}
function wrapCrateSearchAppendix(appendix) {
return [
appendix,
{
content: "remind",
description: `Remind: <dim>We only indexed the top 20K crates. Sorry for the inconvenience if your desired crate not show.</dim>`,
},
];
}
omnibox.bootstrap({
onSearch: (query) => {
return stdSearcher.search(query);
},
onFormat: formatDoc,
onAppend: (query) => {
return [{
content: stdSearcher.getSearchUrl(query),
description: `Search Rust docs <match>${query}</match> on ${isOfflineMode ? "offline mode" : stdSearcher.getRootPath()}`,
}];
},
onEmptyNavigate: (content, disposition) => {
commandManager.handleCommandEnterEvent(content, disposition);
},
beforeNavigate: async (query, content) => {
if (content && /^@\w+$/i.test(content.trim())) {
// Case: @crate, redirect to that crate's docs.rs page
return `https://docs.rs/${content.replace("@", "")}`;
} else if (content && /^https?.*\/~\/\*\/.*/ig.test(content)) {
// Sanitize docs url which from all crates doc search mode. (Prefix with "~")
// Here is the url instance: https://docs.rs/~/*/reqwest/fn.get.html
let [_, __, libName] = new URL(content).pathname.slice(1).split("/");
let crate = await CrateDocManager.getCrateByName(libName);
const crateVersion = await settings.keepCratesUpToDate ? "latest" : crate.version;
return content.replace("/~/", `/${crate.crateName || libName}/`).replace("/*/", `/${crateVersion}/`);
} else {
return content;
}
},
afterNavigated: async (query, result) => {
// Ignore the command history
if (query?.startsWith(":")) return;
// Only keep the latest 100 of search history.
let historyItem = await HistoryCommand.record(query, result, 100);
let statistics = await Statistics.load();
await statistics.record(historyItem, true);
},
});
omnibox.addRegexQueryEvent(/^s(?:rc)?:/i, {
name: "Source code",
onSearch: (query) => {
return stdSearcher.search(query);
},
onFormat: formatDoc,
onAppend: (query) => {
return [{
content: stdSearcher.getSearchUrl(query),
description: `Search Rust docs <match>${query}</match> on ${isOfflineMode ? "offline mode" : stdSearcher.getRootPath()}`,
}];
},
});
// Nightly std docs search
omnibox.addPrefixQueryEvent("/", {
name: "Nightly docs",
onSearch: (query) => {
query = query.replaceAll("/", "").trim();
return nightlySearcher.search(query);
},
onFormat: (index, doc) => {
let { content, description } = formatDoc(index, doc);
return { content, description: '[Nightly] ' + description };
},
onAppend: (query) => {
query = query.replaceAll("/", "").trim();
return [{
content: nightlySearcher.getSearchUrl(query),
description: `Search nightly Rust docs <match>${query}</match> on ${nightlySearcher.getRootPath()}`,
}];
},
});
omnibox.addPrefixQueryEvent("~", {
name: "External docs",
isDefaultSearch: () => {
return defaultSearch.thirdPartyDocs;
},
searchPriority: 1,
onSearch: async (query) => {
return await crateDocSearcher.searchAll(query);
},
onFormat: formatDoc,
});
omnibox.addPrefixQueryEvent("@", {
name: "Crate docs",
onSearch: async (query) => {
return await crateDocSearcher.search(query);
},
onFormat: (index, item) => {
if ('content' in item) {
// 1. Crate list header.
// 2. Crate result footer
return item;
} else if ('href' in item) {
return formatDoc(index, item);
} else {
// Crate name list.
let content = `@${item.name}`;
return {
content,
description: `<match>${content}</match> v${item.version} - <dim>${Compat.escape(Compat.eliminateTags(item.doc))}</dim>`,
};
}
},
onAppend: () => {
return [{
content: chrome.runtime.getURL("manage/crates.html"),
description: `Remind: <dim>Select here to manage all your indexed crates</dim>`,
}];
},
});
omnibox.addPrefixQueryEvent("!", {
name: "docs.rs",
isDefaultSearch: () => {
return defaultSearch.docsRs;
},
searchPriority: 2,
onSearch: (query) => {
return crateSearcher.search(query);
},
onFormat: (index, crate) => {
return {
content: `https://docs.rs/${crate.id}`,
description: `${Compat.capitalize("docs.rs")}: <match>${crate.id}</match> v${crate.version} - <dim>${Compat.escape(Compat.eliminateTags(crate.description))}</dim>`,
};
},
onAppend: (query) => {
let keyword = query.replace(/[!\s]/g, "");
return wrapCrateSearchAppendix({
content: "https://docs.rs/releases/search?query=" + encodeURIComponent(keyword),
description: "Search Rust crates for " + `<match>${keyword}</match>` + " on https://docs.rs",
});
},
});
omnibox.addPrefixQueryEvent("!!", {
name: "crates.io",
onSearch: (query) => {
return crateSearcher.search(query);
},
onFormat: (index, crate) => {
return {
content: `https://${crateRegistry}/crates/${crate.id}`,
description: `${Compat.capitalize(crateRegistry)}: <match>${crate.id}</match> v${crate.version} - <dim>${Compat.escape(Compat.eliminateTags(crate.description))}</dim>`,
};
},
onAppend: (query) => {
let keyword = query.replace(/[!\s]/g, "");
return wrapCrateSearchAppendix({
content: `https://${crateRegistry}/search?q=` + encodeURIComponent(keyword),
description: "Search Rust crates for " + `<match>${keyword}</match>` + ` on https://${crateRegistry}`,
});
},
});
omnibox.addPrefixQueryEvent("!!!", {
name: "Repository",
onSearch: (query) => {
return crateSearcher.search(query);
},
onFormat: (index, crate) => {
return {
content: `${REDIRECT_URL}?crate=${crate.id}`,
description: `${Compat.capitalize("repository")}: <match>${crate.id}</match> v${crate.version} - <dim>${Compat.escape(Compat.eliminateTags(crate.description))}</dim>`,
};
},
onAppend: (query) => {
let keyword = query.replace(/[!\s]/g, "");
return wrapCrateSearchAppendix({
content: "https://github.com/search?q=" + encodeURIComponent(keyword),
description: "Search Rust crates for " + `<match>${keyword}</match>` + " on https://github.com",
});
},
});
omnibox.addPrefixQueryEvent("#", {
name: "Attributes",
isDefaultSearch: () => {
return defaultSearch.attributes;
},
searchPriority: 3,
onSearch: (query) => {
query = query.replace(/[[\]]/g, "");
return attributeSearcher.search(query);
},
onFormat: (index, attribute) => {
return {
content: attribute.href,
description: `Attribute: <match>#[${attribute.name}]</match> <dim>${Compat.escape(attribute.description)}</dim>`,
};
},
});
omnibox.addPrefixQueryEvent("?", {
name: "Can I use",
onSearch: (query) => {
return caniuseSearcher.search(query);
},
onFormat: (index, feat, query) => {
return {
content: `https://caniuse.rs/features/${feat.slug}`,
description: `Can I use: <match>${Compat.escape(feat.match)}</match> [${feat.version}] - <dim>${Compat.escape(feat.description)}</dim>`
};
},
onAppend: () => {
return [{
content: ":rfc",
description: `Remind: <dim>you can use</dim> :rfc <dim>command to search all Rust RFCs.</dim>`,
}];
},
});
omnibox.addRegexQueryEvent(/^`?e\d{2,4}`?$/i, {
name: "Error code",
onSearch: (query) => {
query = query.replace("`", "");
let baseIndex = parseInt(query.slice(1).padEnd(4, '0'));
let result = [];
for (let i = 0; i < 10; i++) {
let errorIndex = 'E' + String(baseIndex++).padStart(4, "0").toUpperCase();
result.push(errorIndex);
}
let baseUrl = isOfflineMode ? offlineDocPath : 'https://doc.rust-lang.org/';
return result.map(errorCode => {
return {
content: `${baseUrl}error_codes/${errorCode}.html`,
description: `Open error code <match>${errorCode}</match> on ${isOfflineMode ? 'offline mode' : 'https://doc.rust-lang.org/error_codes/error-index.html'}`,
};
});
},
});
omnibox.addPrefixQueryEvent("%", {
name: "Books",
onSearch: (query) => {
return bookSearcher.search(query);
},
onFormat: (index, page) => {
let parentTitles = page.parentTitles || [];
return {
content: page.url,
description: `${[...parentTitles.map(t => Compat.escape(t)), `<match>${Compat.escape(page.title)}</match>`].join(" > ")} - <dim>${page.name}</dim>`
}
},
onAppend: () => {
return [{
content: ":book",
description: `Remind: <dim>you can use</dim> :book <dim>command to search all Rust books.</dim>`,
}];
},
});
omnibox.addPrefixQueryEvent(">", {
name: "Clippy lints",
onSearch: (query) => {
return lintSearcher.search(query);
},
onFormat: (_, lint) => {
return {
content: `${LINT_URL}#${lint.name}`,
description: `Clippy lint: [${lint.level}] <match>${lint.name}</match> - <dim>${Compat.escape(Compat.eliminateTags(lint.description))}</dim>`,
}
},
});
omnibox.addPrefixQueryEvent(":", {
name: "Commands",
onSearch: async (query) => {
return commandManager.execute(query);
},
});
}
}

View file

@ -1,5 +1,4 @@
import settings from "./settings.js";
import Statistics from "./statistics.js";
import attributesIndex from "./index/attributes.js";
import IndexManager from "./index-manager.js";
import CrateSearch from "./search/crate.js";
@ -19,14 +18,11 @@ import SimpleCommand from "./core/command/simple.js";
import OpenCommand from "./core/command/open.js";
import HistoryCommand from "./core/command/history.js";
import CommandManager from "./core/command/manager.js";
import CrateDocManager from "./crate-manager.js";
import { Compat } from "./core/index.js";
import {
INDEX_UPDATE_URL,
LINT_URL,
REDIRECT_URL,
RUST_RELEASE_README_URL,
} from "./constants.js";
import RustSearchOmnibox from "./lib.js";
async function start(omnibox) {
@ -96,306 +92,17 @@ async function start(omnibox) {
return "https://doc.rust-lang.org/nightly/";
});
let formatDoc = (index, doc) => {
let content = doc.href;
let description = doc.displayPath + `<match>${doc.name}</match>`;
if (doc.desc) {
description += ` - <dim>${Compat.escape(Compat.eliminateTags(doc.desc))}</dim>`;
}
if (doc.queryType === "s" || doc.queryType === "src") {
let url = new URL(doc.href);
url.search = "?mode=src";
content = url.toString();
description = `[Source code] ${description}`;
}
return { content, description };
};
omnibox.bootstrap({
onSearch: (query) => {
return stdSearcher.search(query);
},
onFormat: formatDoc,
onAppend: (query) => {
return [{
content: stdSearcher.getSearchUrl(query),
description: `Search Rust docs <match>${query}</match> on ${isOfflineMode ? "offline mode" : stdSearcher.getRootPath()}`,
}];
},
onEmptyNavigate: (content, disposition) => {
commandManager.handleCommandEnterEvent(content, disposition);
},
beforeNavigate: async (query, content) => {
if (content && /^@\w+$/i.test(content.trim())) {
// Case: @crate, redirect to that crate's docs.rs page
return `https://docs.rs/${content.replace("@", "")}`;
} else if (content && /^https?.*\/~\/\*\/.*/ig.test(content)) {
// Sanitize docs url which from all crates doc search mode. (Prefix with "~")
// Here is the url instance: https://docs.rs/~/*/reqwest/fn.get.html
let [_, __, libName] = new URL(content).pathname.slice(1).split("/");
let crate = await CrateDocManager.getCrateByName(libName);
const crateVersion = await settings.keepCratesUpToDate ? "latest" : crate.version;
return content.replace("/~/", `/${crate.crateName || libName}/`).replace("/*/", `/${crateVersion}/`);
} else {
return content;
}
},
afterNavigated: async (query, result) => {
// Ignore the command history
if (query?.startsWith(":")) return;
// Only keep the latest 100 of search history.
let historyItem = await HistoryCommand.record(query, result, 100);
let statistics = await Statistics.load();
await statistics.record(historyItem, true);
},
});
omnibox.addRegexQueryEvent(/^s(?:rc)?:/i, {
name: "Source code",
onSearch: (query) => {
return stdSearcher.search(query);
},
onFormat: formatDoc,
onAppend: (query) => {
return [{
content: stdSearcher.getSearchUrl(query),
description: `Search Rust docs <match>${query}</match> on ${isOfflineMode ? "offline mode" : stdSearcher.getRootPath()}`,
}];
},
});
// Nightly std docs search
omnibox.addPrefixQueryEvent("/", {
name: "Nightly docs",
onSearch: (query) => {
query = query.replaceAll("/", "").trim();
return nightlySearcher.search(query);
},
onFormat: (index, doc) => {
let { content, description } = formatDoc(index, doc);
return { content, description: '[Nightly] ' + description };
},
onAppend: (query) => {
query = query.replaceAll("/", "").trim();
return [{
content: nightlySearcher.getSearchUrl(query),
description: `Search nightly Rust docs <match>${query}</match> on ${nightlySearcher.getRootPath()}`,
}];
},
});
omnibox.addPrefixQueryEvent("~", {
name: "External docs",
isDefaultSearch: () => {
return defaultSearch.thirdPartyDocs;
},
searchPriority: 1,
onSearch: async (query) => {
return await crateDocSearcher.searchAll(query);
},
onFormat: formatDoc,
});
omnibox.addPrefixQueryEvent("@", {
name: "Crate docs",
onSearch: async (query) => {
return await crateDocSearcher.search(query);
},
onFormat: (index, item) => {
if ('content' in item) {
// 1. Crate list header.
// 2. Crate result footer
return item;
} else if ('href' in item) {
return formatDoc(index, item);
} else {
// Crate name list.
let content = `@${item.name}`;
return {
content,
description: `<match>${content}</match> v${item.version} - <dim>${Compat.escape(Compat.eliminateTags(item.doc))}</dim>`,
};
}
},
onAppend: () => {
return [{
content: chrome.runtime.getURL("manage/crates.html"),
description: `Remind: <dim>Select here to manage all your indexed crates</dim>`,
}];
},
});
function wrapCrateSearchAppendix(appendix) {
return [
appendix,
{
content: "remind",
description: `Remind: <dim>We only indexed the top 20K crates. Sorry for the inconvenience if your desired crate not show.</dim>`,
},
];
}
omnibox.addPrefixQueryEvent("!", {
name: "docs.rs",
isDefaultSearch: () => {
return defaultSearch.docsRs;
},
searchPriority: 2,
onSearch: (query) => {
return crateSearcher.search(query);
},
onFormat: (index, crate) => {
return {
content: `https://docs.rs/${crate.id}`,
description: `${Compat.capitalize("docs.rs")}: <match>${crate.id}</match> v${crate.version} - <dim>${Compat.escape(Compat.eliminateTags(crate.description))}</dim>`,
};
},
onAppend: (query) => {
let keyword = query.replace(/[!\s]/g, "");
return wrapCrateSearchAppendix({
content: "https://docs.rs/releases/search?query=" + encodeURIComponent(keyword),
description: "Search Rust crates for " + `<match>${keyword}</match>` + " on https://docs.rs",
});
},
});
omnibox.addPrefixQueryEvent("!!", {
name: "crates.io",
onSearch: (query) => {
return crateSearcher.search(query);
},
onFormat: (index, crate) => {
return {
content: `https://${crateRegistry}/crates/${crate.id}`,
description: `${Compat.capitalize(crateRegistry)}: <match>${crate.id}</match> v${crate.version} - <dim>${Compat.escape(Compat.eliminateTags(crate.description))}</dim>`,
};
},
onAppend: (query) => {
let keyword = query.replace(/[!\s]/g, "");
return wrapCrateSearchAppendix({
content: `https://${crateRegistry}/search?q=` + encodeURIComponent(keyword),
description: "Search Rust crates for " + `<match>${keyword}</match>` + ` on https://${crateRegistry}`,
});
},
});
omnibox.addPrefixQueryEvent("!!!", {
name: "Repository",
onSearch: (query) => {
return crateSearcher.search(query);
},
onFormat: (index, crate) => {
return {
content: `${REDIRECT_URL}?crate=${crate.id}`,
description: `${Compat.capitalize("repository")}: <match>${crate.id}</match> v${crate.version} - <dim>${Compat.escape(Compat.eliminateTags(crate.description))}</dim>`,
};
},
onAppend: (query) => {
let keyword = query.replace(/[!\s]/g, "");
return wrapCrateSearchAppendix({
content: "https://github.com/search?q=" + encodeURIComponent(keyword),
description: "Search Rust crates for " + `<match>${keyword}</match>` + " on https://github.com",
});
},
});
omnibox.addPrefixQueryEvent("#", {
name: "Attributes",
isDefaultSearch: () => {
return defaultSearch.attributes;
},
searchPriority: 3,
onSearch: (query) => {
query = query.replace(/[[\]]/g, "");
return attributeSearcher.search(query);
},
onFormat: (index, attribute) => {
return {
content: attribute.href,
description: `Attribute: <match>#[${attribute.name}]</match> <dim>${Compat.escape(attribute.description)}</dim>`,
};
},
});
omnibox.addPrefixQueryEvent("?", {
name: "Can I use",
onSearch: (query) => {
return caniuseSearcher.search(query);
},
onFormat: (index, feat, query) => {
return {
content: `https://caniuse.rs/features/${feat.slug}`,
description: `Can I use: <match>${Compat.escape(feat.match)}</match> [${feat.version}] - <dim>${Compat.escape(feat.description)}</dim>`
};
},
onAppend: () => {
return [{
content: ":rfc",
description: `Remind: <dim>you can use</dim> :rfc <dim>command to search all Rust RFCs.</dim>`,
}];
},
});
omnibox.addRegexQueryEvent(/^`?e\d{2,4}`?$/i, {
name: "Error code",
onSearch: (query) => {
query = query.replace("`", "");
let baseIndex = parseInt(query.slice(1).padEnd(4, '0'));
let result = [];
for (let i = 0; i < 10; i++) {
let errorIndex = 'E' + String(baseIndex++).padStart(4, "0").toUpperCase();
result.push(errorIndex);
}
let baseUrl = isOfflineMode ? offlineDocPath : 'https://doc.rust-lang.org/';
return result.map(errorCode => {
return {
content: `${baseUrl}error_codes/${errorCode}.html`,
description: `Open error code <match>${errorCode}</match> on ${isOfflineMode ? 'offline mode' : 'https://doc.rust-lang.org/error_codes/error-index.html'}`,
};
});
},
});
omnibox.addPrefixQueryEvent("%", {
name: "Books",
onSearch: (query) => {
return bookSearcher.search(query);
},
onFormat: (index, page) => {
let parentTitles = page.parentTitles || [];
return {
content: page.url,
description: `${[...parentTitles.map(t => Compat.escape(t)), `<match>${Compat.escape(page.title)}</match>`].join(" > ")} - <dim>${page.name}</dim>`
}
},
onAppend: () => {
return [{
content: ":book",
description: `Remind: <dim>you can use</dim> :book <dim>command to search all Rust books.</dim>`,
}];
},
});
omnibox.addPrefixQueryEvent(">", {
name: "Clippy lints",
onSearch: (query) => {
return lintSearcher.search(query);
},
onFormat: (_, lint) => {
return {
content: `${LINT_URL}#${lint.name}`,
description: `Clippy lint: [${lint.level}] <match>${lint.name}</match> - <dim>${Compat.escape(Compat.eliminateTags(lint.description))}</dim>`,
}
},
});
omnibox.addPrefixQueryEvent(":", {
name: "Commands",
onSearch: async (query) => {
return commandManager.execute(query);
},
RustSearchOmnibox.run({
omnibox,
stdSearcher,
nightlySearcher,
crateDocSearcher,
crateSearcher,
attributeSearcher,
bookSearcher,
caniuseSearcher,
lintSearcher,
commandManager,
});
if (!omnibox.extensionMode) return;