Step 132: library symbol browser
This commit is contained in:
@@ -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. |
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
120
editor/src/LibraryBrowserPanel.h
Normal file
120
editor/src/LibraryBrowserPanel.h
Normal 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;
|
||||
}
|
||||
@@ -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)
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
37
editor/tests/step132_test.cpp
Normal file
37
editor/tests/step132_test.cpp
Normal 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;
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user