Merge pull request #29 from leanprover/search

Search
main
Henrik Böving 2022-02-13 15:56:08 +01:00 committed by GitHub
commit 7d43c83178
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 115 additions and 9 deletions

View File

@ -9,6 +9,7 @@ import DocGen4.Output.Base
import DocGen4.Output.Index import DocGen4.Output.Index
import DocGen4.Output.Module import DocGen4.Output.Module
import DocGen4.Output.NotFound import DocGen4.Output.NotFound
import DocGen4.Output.Find
namespace DocGen4 namespace DocGen4
@ -66,10 +67,25 @@ def htmlOutput (result : AnalyzerResult) (root : String) : IO Unit := do
let indexHtml := ReaderT.run index config let indexHtml := ReaderT.run index config
let notFoundHtml := ReaderT.run notFound config let notFoundHtml := ReaderT.run notFound config
FS.createDirAll basePath FS.createDirAll basePath
FS.createDirAll (basePath / "find")
let mut declList := #[]
for (_, mod) in result.moduleInfo.toArray do
for decl in mod.members do
let findHtml := ReaderT.run (findRedirectHtml decl.getName) config
let findDir := basePath / "find" / decl.getName.toString
FS.createDirAll findDir
FS.writeFile (findDir / "index.html") findHtml.toString
let obj := Json.mkObj [("name", decl.getName.toString), ("description", decl.getDocString.getD "")]
declList := declList.push obj
let json := Json.arr declList
FS.writeFile (basePath / "searchable_data.bmp") json.compress
FS.writeFile (basePath / "index.html") indexHtml.toString FS.writeFile (basePath / "index.html") indexHtml.toString
FS.writeFile (basePath / "style.css") styleCss FS.writeFile (basePath / "style.css") styleCss
FS.writeFile (basePath / "404.html") notFoundHtml.toString FS.writeFile (basePath / "404.html") notFoundHtml.toString
FS.writeFile (basePath / "nav.js") navJs FS.writeFile (basePath / "nav.js") navJs
FS.writeFile (basePath / "search.js") searchJs
for (module, content) in result.moduleInfo.toArray do for (module, content) in result.moduleInfo.toArray do
let moduleHtml := ReaderT.run (moduleToHtml content) config let moduleHtml := ReaderT.run (moduleToHtml content) config
let path := moduleNameToFile basePath module let path := moduleNameToFile basePath module

View File

@ -48,6 +48,7 @@ def moduleNameToDirectory (basePath : FilePath) (n : Name) : FilePath :=
section Static section Static
def styleCss : String := include_str "./static/style.css" def styleCss : String := include_str "./static/style.css"
def navJs : String := include_str "./static/nav.js" def navJs : String := include_str "./static/nav.js"
def searchJs : String := include_str "./static/search.js"
end Static end Static
def declNameToLink (name : Name) : HtmlM String := do def declNameToLink (name : Name) : HtmlM String := do

15
DocGen4/Output/Find.lean Normal file
View File

@ -0,0 +1,15 @@
import DocGen4.Output.Template
namespace DocGen4
namespace Output
open scoped DocGen4.Jsx
open Lean
def findRedirectHtml (decl : Name) : HtmlM Html := do
let res ← getResult
let url ← declNameToLink decl
let contentString := s!"0;url={url}"
pure $ Html.element "meta" false #[("http-equiv", "refresh"), ("content", contentString)] #[]
end Output
end DocGen4

View File

@ -49,6 +49,7 @@ def baseHtmlArray (title : String) (site : Array Html) : HtmlM Html := do
-- TODO Add more js stuff -- TODO Add more js stuff
<script src={s!"{←getRoot}nav.js"}></script> <script src={s!"{←getRoot}nav.js"}></script>
<script src={s!"{←getRoot}search.js"}></script>
</body> </body>
</html> </html>

View File

@ -206,7 +206,9 @@ def valueToEq (v : DefinitionVal) : MetaM Expr := withLCtx {} {} do
def computable? (defn : Name) : MetaM Bool := do def computable? (defn : Name) : MetaM Bool := do
let cstage2Name := defn.append `_cstage2 let cstage2Name := defn.append `_cstage2
let env ← getEnv let env ← getEnv
pure $ env.find? cstage2Name |>.isSome let extern? := externAttr.getParam env defn |>.isSome
let cstage2? := env.find? cstage2Name |>.isSome
pure $ extern? cstage2?
def DefinitionInfo.ofDefinitionVal (v : DefinitionVal) : MetaM DefinitionInfo := do def DefinitionInfo.ofDefinitionVal (v : DefinitionVal) : MetaM DefinitionInfo := do
let info ← Info.ofConstantVal v.toConstantVal let info ← Info.ofConstantVal v.toConstantVal
@ -444,6 +446,17 @@ def getAttrs : DocInfo → Array String
| classInfo i => i.attrs | classInfo i => i.attrs
| classInductiveInfo i => i.attrs | classInductiveInfo i => i.attrs
def getDocString : DocInfo → Option String
| axiomInfo i => i.doc
| theoremInfo i => i.doc
| opaqueInfo i => i.doc
| definitionInfo i => i.doc
| instanceInfo i => i.doc
| inductiveInfo i => i.doc
| structureInfo i => i.doc
| classInfo i => i.doc
| classInductiveInfo i => i.doc
end DocInfo end DocInfo
structure AnalyzerResult where structure AnalyzerResult where

View File

@ -106,14 +106,23 @@ if (tse != null) {
// Simple declaration search // Simple declaration search
// ------------------------- // -------------------------
const searchWorkerURL = new URL(`${siteRoot}searchWorker.js`, window.location); const declURL = new URL(`${siteRoot}searchable_data.bmp`, window.location);
const declSearch = (q) => new Promise((resolve, reject) => { const getDecls = (() => {
const worker = new SharedWorker(searchWorkerURL); let decls;
worker.port.start(); return () => {
worker.port.onmessage = ({data}) => resolve(data); if (!decls) decls = new Promise((resolve, reject) => {
worker.port.onmessageerror = (e) => reject(e); const req = new XMLHttpRequest();
worker.port.postMessage({q}); req.responseType = 'json';
}); req.addEventListener('load', () => resolve(loadDecls(req.response)));
req.addEventListener('error', () => reject());
req.open('GET', declURL);
req.send();
})
return decls;
}
})()
const declSearch = async (q) => getMatches(await getDecls(), q);
const srId = 'search_results'; const srId = 'search_results';
document.getElementById('search_form') document.getElementById('search_form')

51
static/search.js Normal file
View File

@ -0,0 +1,51 @@
function isSep(c) {
return c === '.' || c === '_';
}
function matchCaseSensitive(declName, lowerDeclName, pat) {
let i = 0, j = 0, err = 0, lastMatch = 0
while (i < declName.length && j < pat.length) {
if (pat[j] === declName[i] || pat[j] === lowerDeclName[i]) {
err += (isSep(pat[j]) ? 0.125 : 1) * (i - lastMatch);
if (pat[j] !== declName[i]) err += 0.5;
lastMatch = i + 1;
j++;
} else if (isSep(declName[i])) {
err += 0.125 * (i + 1 - lastMatch);
lastMatch = i + 1;
}
i++;
}
err += 0.125 * (declName.length - lastMatch);
if (j === pat.length) {
return err;
}
}
function loadDecls(searchableDataCnt) {
return searchableDataCnt.map(({name, description}) => [name, name.toLowerCase(), description.toLowerCase()])
}
function getMatches(decls, pat, maxResults = 30) {
const lowerPats = pat.toLowerCase().split(/\s/g);
const patNoSpaces = pat.replace(/\s/g, '');
const results = [];
for (const [decl, lowerDecl, lowerDoc] of decls) {
let err = matchCaseSensitive(decl, lowerDecl, patNoSpaces);
// match all words as substrings of docstring
if (!(err < 3) && pat.length > 3 && lowerPats.every(l => lowerDoc.indexOf(l) != -1)) {
err = 3;
}
if (err !== undefined) {
results.push({decl, err});
}
}
return results.sort(({err: a}, {err: b}) => a - b).slice(0, maxResults);
}
if (typeof process === 'object') { // NodeJS
const data = loadDecls(JSON.parse(require('fs').readFileSync('searchable_data.bmp').toString()));
console.log(getMatches(data, process.argv[2] || 'ltltle'));
}