#pragma once #include "imgui.h" #include "LibraryIndexer.h" #include "ast/ExternalModule.h" #include "NotificationSystem.h" #include #include #include #include struct LibraryBrowserState { char filterBuf[128] = {}; std::string selectedLibrary; std::string selectedSymbol; std::string docText; std::vector activeTags; }; static std::string formatUsageTemplate(const std::string& symbol) { if (symbol.empty()) return symbol; if (std::isupper((unsigned char)symbol[0])) return symbol; if (symbol.find('(') != std::string::npos) return symbol; return symbol + "()"; } static void updateDocText(LibraryBrowserState& state, const LibraryIndexData& index, const std::string& library, const std::string& symbol) { state.docText.clear(); auto it = index.symbolsByLibrary.find(library); if (it != index.symbolsByLibrary.end()) { for (const auto& sym : it->second) { if (sym.name == symbol) { if (!sym.detail.empty()) { state.docText = sym.detail; } break; } } } if (state.docText.empty()) { state.docText = "No documentation available."; } } static bool symbolMatchesFilter(const std::string& symbol, const std::string& filter) { if (filter.empty()) return true; std::string a = symbol; std::string b = filter; std::transform(a.begin(), a.end(), a.begin(), [](unsigned char c) { return (char)std::tolower(c); }); std::transform(b.begin(), b.end(), b.begin(), [](unsigned char c) { return (char)std::tolower(c); }); return a.find(b) != std::string::npos; } static std::string joinTags(const std::vector& tags) { std::string out; for (size_t i = 0; i < tags.size(); ++i) { if (i > 0) out += " "; out += tags[i]; } return out; } static std::vector combinedTags(const ExternalModule* ext, const TypeSignature* sig) { std::vector out = ext ? ext->semanticTags : std::vector(); if (sig) { out.insert(out.end(), sig->semanticTags.begin(), sig->semanticTags.end()); } std::sort(out.begin(), out.end()); out.erase(std::unique(out.begin(), out.end()), out.end()); return out; } static bool symbolMatchesTags(const std::vector& tags, const std::vector& active) { if (active.empty()) return true; for (const auto& tag : active) { if (std::find(tags.begin(), tags.end(), tag) == tags.end()) return false; } return true; } static std::vector collectAvailableTags(Module* ast) { std::vector tags; if (!ast) return tags; for (auto* node : ast->getChildren("externalModules")) { auto* ext = static_cast(node); for (const auto& tag : ext->semanticTags) tags.push_back(tag); for (auto* sigNode : ext->getChildren("signatures")) { auto* sig = static_cast(sigNode); for (const auto& tag : sig->semanticTags) tags.push_back(tag); } } std::sort(tags.begin(), tags.end()); tags.erase(std::unique(tags.begin(), tags.end()), tags.end()); return tags; } static bool renderLibraryBrowser(LibraryBrowserState& state, Module* ast, const LibraryIndexData& index, std::string& outInsert, std::string& outLibrary, NotificationSystem& notifications) { outInsert.clear(); outLibrary.clear(); if (!ast) { ImGui::TextDisabled("(no structured AST)"); return false; } auto availableTags = collectAvailableTags(ast); if (!availableTags.empty()) { if (!state.activeTags.empty()) { ImGui::TextUnformatted("Active tags"); for (const auto& tag : state.activeTags) { ImGui::PushID(tag.c_str()); ImGui::TextDisabled("%s", tag.c_str()); ImGui::SameLine(); if (ImGui::SmallButton("x")) { state.activeTags.erase(std::remove(state.activeTags.begin(), state.activeTags.end(), tag), state.activeTags.end()); ImGui::PopID(); break; } ImGui::SameLine(); ImGui::PopID(); } ImGui::NewLine(); } ImGui::TextUnformatted("Filter by tag"); for (const auto& tag : availableTags) { bool active = std::find(state.activeTags.begin(), state.activeTags.end(), tag) != state.activeTags.end(); if (active) ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive)); if (ImGui::SmallButton(tag.c_str())) { if (active) { state.activeTags.erase(std::remove(state.activeTags.begin(), state.activeTags.end(), tag), state.activeTags.end()); } else { state.activeTags.push_back(tag); } } if (active) ImGui::PopStyleColor(); ImGui::SameLine(); } ImGui::NewLine(); } ImGui::InputText("Filter", state.filterBuf, sizeof(state.filterBuf)); std::string filter = state.filterBuf; ImGui::Separator(); bool inserted = false; auto modules = ast->getChildren("externalModules"); if (modules.empty()) { ImGui::TextDisabled("(no libraries indexed)"); return false; } for (auto* node : modules) { auto* ext = static_cast(node); ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanFullWidth; bool open = ImGui::TreeNodeEx(ext->name.c_str(), flags); if (open) { auto sigs = ext->getChildren("signatures"); for (auto* sigNode : sigs) { auto* sig = static_cast(sigNode); auto tags = combinedTags(ext, sig); if (!symbolMatchesFilter(sig->name, filter)) continue; if (!symbolMatchesTags(tags, state.activeTags)) continue; std::string label = sig->name; bool selected = (state.selectedLibrary == ext->name && state.selectedSymbol == sig->name); if (ImGui::Selectable(label.c_str(), selected)) { state.selectedLibrary = ext->name; state.selectedSymbol = sig->name; updateDocText(state, index, ext->name, sig->name); } if (!tags.empty()) { ImGui::SameLine(); ImGui::TextDisabled("%s", joinTags(tags).c_str()); } if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) { std::string usage = formatUsageTemplate(sig->name); ImGui::SetDragDropPayload("WhetstoneSymbol", usage.c_str(), usage.size() + 1); ImGui::TextUnformatted(usage.c_str()); ImGui::EndDragDropSource(); } } ImGui::TreePop(); } } if (!state.selectedSymbol.empty()) { ImGui::Separator(); ImGui::Text("Selected: %s", state.selectedSymbol.c_str()); for (auto* node : modules) { auto* ext = static_cast(node); if (ext->name != state.selectedLibrary) continue; for (auto* sigNode : ext->getChildren("signatures")) { auto* sig = static_cast(sigNode); if (sig->name != state.selectedSymbol) continue; auto tags = combinedTags(ext, sig); if (!tags.empty()) { ImGui::TextDisabled("Tags: %s", joinTags(tags).c_str()); } break; } break; } ImGui::TextWrapped("%s", state.docText.c_str()); std::string usage = formatUsageTemplate(state.selectedSymbol); if (ImGui::Button("Insert")) { outInsert = usage; outLibrary = state.selectedLibrary; notifications.notify(NotificationLevel::Success, "[libs] Inserted " + usage); inserted = true; } ImGui::SameLine(); ImGui::TextDisabled("%s", usage.c_str()); } return inserted; }