Step 132: library symbol browser

This commit is contained in:
Bill
2026-02-09 17:05:09 -07:00
parent ae95aeb303
commit dcc963f625
7 changed files with 194 additions and 2 deletions

View File

@@ -382,7 +382,7 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi
## What's Next
Sprint 5 in progress. Step 131 (library API indexing via type stubs) done. Next: Step 132 (library symbol browser).
Sprint 5 in progress. Step 132 (library symbol browser) done. Next: Step 133 (import statement generation).
---
@@ -477,3 +477,4 @@ Sprint 5 in progress. Step 131 (library API indexing via type stubs) done. Next:
| 2026-02-09 | Codex | Step 129: Dependency management UI panel with add/remove/update, source targeting, and writeback support for requirements.txt/package.json/Cargo.toml/go.mod/vcpkg.json. 8/8 tests pass. |
| 2026-02-09 | Codex | Step 130: LSP workspace symbol + completion indexing for dependencies with ExternalModule population and library index polling. 7/7 tests pass. |
| 2026-02-09 | Codex | Step 131: Stub-based library indexing (pyi/d.ts/headers/lib.rs) with workspace scan fallback. 8/8 tests pass. |
| 2026-02-09 | Codex | Step 132: Library symbol browser panel with filtering, doc detail display, and insert template helpers. 5/5 tests pass. |

View File

@@ -740,6 +740,12 @@ add_executable(step131_test tests/step131_test.cpp)
target_include_directories(step131_test PRIVATE src)
target_link_libraries(step131_test PRIVATE nlohmann_json::nlohmann_json)
add_executable(step132_test tests/step132_test.cpp)
target_include_directories(step132_test PRIVATE src)
target_link_libraries(step132_test PRIVATE
imgui::imgui
nlohmann_json::nlohmann_json)
find_package(SDL2 CONFIG REQUIRED)
find_package(OpenGL REQUIRED)
find_package(glad CONFIG REQUIRED)

View File

@@ -45,6 +45,7 @@
#include "BuildSystem.h"
#include "DependencyPanel.h"
#include "LibraryIndexer.h"
#include "LibraryBrowserPanel.h"
#include "IncrementalOptimizer.h"
#include "ast/Serialization.h"
#include "ast/Generator.h"
@@ -167,6 +168,8 @@ struct EditorState {
std::string lastBuildCommand;
bool showDependencyPanel = true;
DependencyPanelState dependencyPanel;
bool showLibraryBrowserPanel = true;
LibraryBrowserState libraryBrowser;
struct LibraryIndexRequest {
std::string name;
std::string version;
@@ -355,6 +358,15 @@ struct EditorState {
onTextChanged();
}
void insertTextAtCursor(const std::string& text) {
if (!active() || text.empty()) return;
bool changed = false;
active()->widget.insertText(active()->editBuf, text, changed);
if (changed) {
onTextChanged();
}
}
std::string makeUntitledName() const {
if (!buffers.hasBuffer("(untitled)")) return "(untitled)";
int i = 1;

View File

@@ -0,0 +1,120 @@
#pragma once
#include "imgui.h"
#include "LibraryIndexer.h"
#include "ast/ExternalModule.h"
#include <string>
#include <vector>
#include <algorithm>
#include <cctype>
struct LibraryBrowserState {
char filterBuf[128] = {};
std::string selectedLibrary;
std::string selectedSymbol;
std::string docText;
};
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 bool renderLibraryBrowser(LibraryBrowserState& state,
Module* ast,
const LibraryIndexData& index,
std::string& outInsert,
std::string& outputLog) {
outInsert.clear();
if (!ast) {
ImGui::TextDisabled("(no structured AST)");
return false;
}
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<ExternalModule*>(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<TypeSignature*>(sigNode);
if (!symbolMatchesFilter(sig->name, filter)) 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 (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());
ImGui::TextWrapped("%s", state.docText.c_str());
std::string usage = formatUsageTemplate(state.selectedSymbol);
if (ImGui::Button("Insert")) {
outInsert = usage;
outputLog += "[libs] Inserted " + usage + "\n";
inserted = true;
}
ImGui::SameLine();
ImGui::TextDisabled("%s", usage.c_str());
}
return inserted;
}

View File

@@ -305,6 +305,7 @@ int main(int, char**) {
ImGui::MenuItem("Terminal", state.keys.getBinding("view.toggleTerminal").toString().c_str(),
&state.showTerminalPanel);
ImGui::MenuItem("Dependencies", nullptr, &state.showDependencyPanel);
ImGui::MenuItem("Libraries", nullptr, &state.showLibraryBrowserPanel);
ImGui::MenuItem("Settings", nullptr, &state.showSettingsPanel);
ImGui::MenuItem("LSP Servers...", nullptr, &state.showLspSettings);
if (state.active()) {
@@ -568,6 +569,21 @@ int main(int, char**) {
}
}
if (state.showLibraryBrowserPanel) {
ImGui::Begin("Libraries", &state.showLibraryBrowserPanel);
ImGui::PushFont(uiFont);
std::string insertText;
if (renderLibraryBrowser(state.libraryBrowser,
state.activeAST(),
state.libraryIndex,
insertText,
state.outputLog)) {
state.insertTextAtCursor(insertText);
}
ImGui::PopFont();
ImGui::End();
}
// ---------------------------------------------------------------
// Find / Replace bar (floating at top of editor)
// ---------------------------------------------------------------

View File

@@ -0,0 +1,37 @@
// Step 132 TDD Test: Library symbol browser panel helpers
#include "LibraryBrowserPanel.h"
#include "ast/ExternalModule.h"
#include "ast/TypeSignature.h"
#include <iostream>
static void expect(bool cond, const std::string& name, int& passed, int& failed) {
if (cond) {
std::cout << "Test " << (passed + failed + 1) << " PASS: " << name << "\n";
++passed;
} else {
std::cout << "Test " << (passed + failed + 1) << " FAIL: " << name << "\n";
++failed;
}
}
int main() {
int passed = 0;
int failed = 0;
expect(formatUsageTemplate("foo") == "foo()", "usage template adds parens", passed, failed);
expect(formatUsageTemplate("Widget") == "Widget", "usage template keeps class", passed, failed);
expect(symbolMatchesFilter("numpy.array", "ARRAY"), "filter is case-insensitive", passed, failed);
LibraryIndexData index;
index.symbolsByLibrary["numpy"].push_back({"numpy.array", "numpy", "Create array", 0});
LibraryBrowserState state;
updateDocText(state, index, "numpy", "numpy.array");
expect(state.docText == "Create array", "doc text from LSP detail", passed, failed);
LibraryBrowserState fallbackState;
updateDocText(fallbackState, index, "missing", "missing");
expect(fallbackState.docText == "No documentation available.", "fallback doc text", passed, failed);
std::cout << "\n=== Step 132 Results: " << passed << " passed, " << failed << " failed ===\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -71,7 +71,7 @@ The foundation: import libraries, parse their APIs, index their symbols.
`TypeSignature` AST nodes attached to `ExternalModule`.
*New:* `StubParser.h`, modifies `ExternalModule` AST concept
- [ ] **Step 132: Library symbol browser**
- [x] **Step 132: Library symbol browser**
New panel or Explorer sub-section: "Libraries". Tree view of imported
libraries → modules → classes/functions → parameters. Click to see
documentation (from LSP hover or stub docstrings). Search/filter box.