Skip to content

Commit c5a7383

Browse files
committed
feat(mcp): Add search and get item tools for Zig standard library
1 parent c8ff6d6 commit c5a7383

File tree

2 files changed

+292
-38
lines changed

2 files changed

+292
-38
lines changed

mcp/std.ts

Lines changed: 169 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ const LOG_warn = 1;
1515
const LOG_info = 2;
1616
const LOG_debug = 3;
1717

18-
const domContent: any = document.getElementById("content");
19-
const domSearch: any = document.getElementById("search");
20-
const domErrors: any = document.getElementById("errors");
21-
const domErrorsText: any = document.getElementById("errorsText");
18+
const domContent: any = typeof document !== "undefined" ? document.getElementById("content") : null;
19+
const domSearch: any = typeof document !== "undefined" ? document.getElementById("search") : null;
20+
const domErrors: any = typeof document !== "undefined" ? document.getElementById("errors") : null;
21+
const domErrorsText: any =
22+
typeof document !== "undefined" ? document.getElementById("errorsText") : null;
2223

2324
var searchTimer: any = null;
2425

@@ -56,8 +57,8 @@ export function startDocsViewer() {
5657
switch (level) {
5758
case LOG_err:
5859
console.error(msg);
59-
domErrorsText.textContent += msg + "\n";
60-
domErrors.classList.remove("hidden");
60+
if (domErrorsText) domErrorsText.textContent += msg + "\n";
61+
if (domErrors) domErrors.classList.remove("hidden");
6162
break;
6263
case LOG_warn:
6364
console.warn(msg);
@@ -73,7 +74,7 @@ export function startDocsViewer() {
7374
},
7475
}).then((obj) => {
7576
wasm_exports = obj.instance.exports;
76-
window.wasm = obj; // for debugging
77+
if (typeof window !== "undefined") window.wasm = obj; // for debugging
7778

7879
sources_promise.then((buffer) => {
7980
const js_array = new Uint8Array(buffer);
@@ -84,16 +85,21 @@ export function startDocsViewer() {
8485

8586
updateModuleList();
8687

87-
window.addEventListener("popstate", onPopState, false);
88-
domSearch.addEventListener("keydown", onSearchKeyDown, false);
89-
domSearch.addEventListener("input", onSearchChange, false);
90-
window.addEventListener("keydown", onWindowKeyDown, false);
88+
if (typeof window !== "undefined") {
89+
window.addEventListener("popstate", onPopState, false);
90+
window.addEventListener("keydown", onWindowKeyDown, false);
91+
}
92+
if (domSearch) {
93+
domSearch.addEventListener("keydown", onSearchKeyDown, false);
94+
domSearch.addEventListener("input", onSearchChange, false);
95+
}
9196
onHashChange(null);
9297
});
9398
});
9499
}
95100

96101
function renderTitle() {
102+
if (typeof document === "undefined") return;
97103
const suffix = " - Zig Documentation";
98104
if (curNavSearch.length > 0) {
99105
document.title = curNavSearch + " - Search" + suffix;
@@ -108,7 +114,7 @@ function renderTitle() {
108114

109115
function render() {
110116
renderTitle();
111-
domContent.textContent = "";
117+
if (domContent) domContent.textContent = "";
112118

113119
if (curNavSearch !== "") return renderSearch();
114120

@@ -130,7 +136,7 @@ function render() {
130136

131137
function renderHome() {
132138
if (moduleList.length == 0) {
133-
domContent.textContent = "# Error\n\nsources.tar contains no modules";
139+
if (domContent) domContent.textContent = "# Error\n\nsources.tar contains no modules";
134140
return;
135141
}
136142
return renderModule(0);
@@ -174,7 +180,8 @@ function renderSource(path: any) {
174180
markdown += "# " + path + "\n\n";
175181
markdown += unwrapString(wasm_exports.decl_source_html(decl_index));
176182

177-
domContent.textContent = markdown;
183+
if (domContent) domContent.textContent = markdown;
184+
return markdown;
178185
}
179186

180187
function renderNamespacePage(decl_index: any) {
@@ -198,7 +205,8 @@ function renderNamespacePage(decl_index: any) {
198205
const fields = declFields(decl_index).slice();
199206
markdown += renderNamespaceMarkdown(decl_index, members, fields);
200207

201-
domContent.textContent = markdown;
208+
if (domContent) domContent.textContent = markdown;
209+
return markdown;
202210
}
203211

204212
function renderFunction(decl_index: any) {
@@ -259,7 +267,8 @@ function renderFunction(decl_index: any) {
259267
markdown += "## Source Code\n\n" + source + "\n\n";
260268
}
261269

262-
domContent.textContent = markdown;
270+
if (domContent) domContent.textContent = markdown;
271+
return markdown;
263272
}
264273

265274
function renderGlobal(decl_index: any) {
@@ -284,7 +293,8 @@ function renderGlobal(decl_index: any) {
284293
markdown += "## Source Code\n\n" + source + "\n\n";
285294
}
286295

287-
domContent.textContent = markdown;
296+
if (domContent) domContent.textContent = markdown;
297+
return markdown;
288298
}
289299

290300
function renderTypeFunction(decl_index: any) {
@@ -331,7 +341,8 @@ function renderTypeFunction(decl_index: any) {
331341
}
332342
}
333343

334-
domContent.textContent = markdown;
344+
if (domContent) domContent.textContent = markdown;
345+
return markdown;
335346
}
336347

337348
function renderErrorSetPage(decl_index: any) {
@@ -360,7 +371,8 @@ function renderErrorSetPage(decl_index: any) {
360371
}
361372
}
362373

363-
domContent.textContent = markdown;
374+
if (domContent) domContent.textContent = markdown;
375+
return markdown;
364376
}
365377

366378
function renderNavMarkdown(decl_index: any) {
@@ -541,7 +553,9 @@ function renderNamespaceMarkdown(base_decl: any, members: any, fields: any) {
541553
}
542554

543555
function renderNotFound() {
544-
domContent.textContent = "# Error\n\nDeclaration not found.";
556+
const markdown = "# Error\n\nDeclaration not found.";
557+
if (domContent) domContent.textContent = markdown;
558+
return markdown;
545559
}
546560

547561
function renderSearch() {
@@ -562,7 +576,8 @@ function renderSearch() {
562576
markdown += "No results found.\n\nPress escape to exit search.";
563577
}
564578

565-
domContent.textContent = markdown;
579+
if (domContent) domContent.textContent = markdown;
580+
return markdown;
566581
}
567582

568583
// Event handlers and utility functions (unchanged from original)
@@ -597,9 +612,9 @@ function updateCurNav(location_hash: any) {
597612
}
598613

599614
function onHashChange(state: any) {
600-
history.replaceState({}, "");
601-
navigate(location.hash);
602-
if (state == null) window.scrollTo({ top: 0 });
615+
if (typeof history !== "undefined") history.replaceState({}, "");
616+
if (typeof location !== "undefined") navigate(location.hash);
617+
if (state == null && typeof window !== "undefined") window.scrollTo({ top: 0 });
603618
}
604619

605620
function onPopState(ev: any) {
@@ -608,7 +623,7 @@ function onPopState(ev: any) {
608623

609624
function navigate(location_hash: any) {
610625
updateCurNav(location_hash);
611-
if (domSearch.value !== curNavSearch) {
626+
if (domSearch && domSearch.value !== curNavSearch) {
612627
domSearch.value = curNavSearch;
613628
}
614629
render();
@@ -619,14 +634,16 @@ function onSearchKeyDown(ev: any) {
619634
case "Enter":
620635
if (ev.shiftKey || ev.ctrlKey || ev.altKey) return;
621636
clearAsyncSearch();
622-
location.hash = computeSearchHash();
637+
if (typeof location !== "undefined") location.hash = computeSearchHash();
623638
ev.preventDefault();
624639
ev.stopPropagation();
625640
return;
626641
case "Escape":
627642
if (ev.shiftKey || ev.ctrlKey || ev.altKey) return;
628-
domSearch.value = "";
629-
domSearch.blur();
643+
if (domSearch) {
644+
domSearch.value = "";
645+
domSearch.blur();
646+
}
630647
ev.preventDefault();
631648
ev.stopPropagation();
632649
startSearch();
@@ -645,8 +662,10 @@ function onWindowKeyDown(ev: any) {
645662
switch (ev.code) {
646663
case "KeyS":
647664
if (ev.shiftKey || ev.ctrlKey || ev.altKey) return;
648-
domSearch.focus();
649-
domSearch.select();
665+
if (domSearch) {
666+
domSearch.focus();
667+
domSearch.select();
668+
}
650669
ev.preventDefault();
651670
ev.stopPropagation();
652671
startAsyncSearch();
@@ -667,6 +686,7 @@ function startAsyncSearch() {
667686
}
668687

669688
function computeSearchHash() {
689+
if (typeof location === "undefined" || !domSearch) return "";
670690
const oldWatHash = location.hash;
671691
const oldHash = oldWatHash.startsWith("#") ? oldWatHash : "#" + oldWatHash;
672692
const parts = oldHash.split("?");
@@ -791,3 +811,122 @@ function setInputString(s: any) {
791811
const wasmArray = new Uint8Array(wasm_exports.memory.buffer, ptr, len);
792812
wasmArray.set(jsArray);
793813
}
814+
815+
export async function searchStdLib(
816+
wasmPath: string,
817+
stdSources: Uint8Array<ArrayBuffer>,
818+
query: string,
819+
limit: number = 20,
820+
): Promise<string> {
821+
const fs = await import("node:fs");
822+
const wasmBytes = fs.readFileSync(wasmPath);
823+
824+
const wasmModule = await WebAssembly.instantiate(wasmBytes, {
825+
js: {
826+
log: (level: any, ptr: any, len: any) => {
827+
const msg = decodeString(ptr, len);
828+
if (level === LOG_err) {
829+
throw new Error(msg);
830+
}
831+
},
832+
},
833+
});
834+
835+
const exports = wasmModule.instance.exports as any;
836+
wasm_exports = exports;
837+
838+
const ptr = exports.alloc(stdSources.length);
839+
const wasmArray = new Uint8Array(exports.memory.buffer, ptr, stdSources.length);
840+
wasmArray.set(stdSources);
841+
exports.unpack(ptr, stdSources.length);
842+
843+
const ignoreCase = query.toLowerCase() === query;
844+
const results = executeQuery(query, ignoreCase);
845+
846+
let markdown = `# Search Results\n\nQuery: "${query}"\n\n`;
847+
848+
if (results.length > 0) {
849+
const limitedResults = results.slice(0, limit);
850+
markdown += `Found ${results.length} results (showing ${limitedResults.length}):\n\n`;
851+
for (let i = 0; i < limitedResults.length; i++) {
852+
const match = limitedResults[i];
853+
const full_name = fullyQualifiedName(match);
854+
markdown += `- ${full_name}\n`;
855+
}
856+
} else {
857+
markdown += "No results found.";
858+
}
859+
860+
return markdown;
861+
}
862+
863+
export async function getStdLibItem(
864+
wasmPath: string,
865+
stdSources: Uint8Array<ArrayBuffer>,
866+
name: string,
867+
getSourceCode: boolean = false,
868+
): Promise<string> {
869+
const fs = await import("node:fs");
870+
const wasmBytes = fs.readFileSync(wasmPath);
871+
872+
const wasmModule = await WebAssembly.instantiate(wasmBytes, {
873+
js: {
874+
log: (level: any, ptr: any, len: any) => {
875+
const msg = decodeString(ptr, len);
876+
if (level === LOG_err) {
877+
throw new Error(msg);
878+
}
879+
},
880+
},
881+
});
882+
883+
const exports = wasmModule.instance.exports as any;
884+
wasm_exports = exports;
885+
886+
const ptr = exports.alloc(stdSources.length);
887+
const wasmArray = new Uint8Array(exports.memory.buffer, ptr, stdSources.length);
888+
wasmArray.set(stdSources);
889+
exports.unpack(ptr, stdSources.length);
890+
891+
const decl_index = findDecl(name);
892+
if (decl_index === null) {
893+
return `# Error\n\nDeclaration "${name}" not found.`;
894+
}
895+
896+
if (getSourceCode) {
897+
const source = unwrapString(exports.decl_source_html(decl_index));
898+
if (source.length > 0) {
899+
return `# ${name} - Source Code\n\n${source}`;
900+
} else {
901+
return `# ${name}\n\nNo source code available.`;
902+
}
903+
}
904+
905+
const category = exports.categorize_decl(decl_index, 0);
906+
switch (category) {
907+
case CAT_namespace:
908+
case CAT_container:
909+
return renderNamespacePage(decl_index);
910+
case CAT_global_variable:
911+
case CAT_primitive:
912+
case CAT_global_const:
913+
case CAT_type:
914+
case CAT_type_type:
915+
return renderGlobal(decl_index);
916+
case CAT_function:
917+
return renderFunction(decl_index);
918+
case CAT_type_function:
919+
return renderTypeFunction(decl_index);
920+
case CAT_error_set:
921+
return renderErrorSetPage(decl_index);
922+
case CAT_alias:
923+
return getStdLibItem(
924+
wasmPath,
925+
stdSources,
926+
fullyQualifiedName(exports.get_aliasee()),
927+
getSourceCode,
928+
);
929+
default:
930+
return `# Error\n\nUnrecognized category ${category} for "${name}".`;
931+
}
932+
}

0 commit comments

Comments
 (0)