Step 112: breadcrumb navigation
This commit is contained in:
@@ -215,6 +215,7 @@ All 38 steps implemented and passing. Each step has a corresponding test (`step1
|
||||
- [x] Step 109: **IMPLEMENTED** — Command palette with fuzzy search and MRU ranking (2/2 tests pass)
|
||||
- [x] Step 110: **IMPLEMENTED** — Go-to-definition (LSP + Whetstone) with hover preview (2/2 tests pass)
|
||||
- [x] Step 111: **IMPLEMENTED** — Symbol outline panel with LSP + AST fallback (2/2 tests pass)
|
||||
- [x] Step 112: **IMPLEMENTED** — Breadcrumb navigation with scope roles (1/1 tests pass)
|
||||
|
||||
---
|
||||
|
||||
@@ -309,6 +310,7 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi
|
||||
**Step 109:** Compile and pass (2/2)
|
||||
**Step 110:** Compile and pass (2/2)
|
||||
**Step 111:** Compile and pass (2/2)
|
||||
**Step 112:** Compile and pass (1/1)
|
||||
|
||||
---
|
||||
|
||||
@@ -360,7 +362,7 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi
|
||||
|
||||
## What's Next
|
||||
|
||||
Sprint 4 in progress. Step 111 (symbol outline panel) done. Next: Step 112 (breadcrumb navigation).
|
||||
Sprint 4 in progress. Step 112 (breadcrumb navigation) done. Next: Step 113 (project-wide search).
|
||||
|
||||
---
|
||||
|
||||
@@ -433,3 +435,4 @@ Sprint 4 in progress. Step 111 (symbol outline panel) done. Next: Step 112 (brea
|
||||
| 2026-02-09 | Codex | Step 109: Command palette with fuzzy search and MRU ranking. 2/2 tests pass. |
|
||||
| 2026-02-09 | Codex | Step 110: Go-to-definition (LSP + Whetstone) with hover preview. 2/2 tests pass. |
|
||||
| 2026-02-09 | Codex | Step 111: Symbol outline panel with LSP + AST fallback. 2/2 tests pass. |
|
||||
| 2026-02-09 | Codex | Step 112: Breadcrumb navigation with scope roles. 1/1 tests pass. |
|
||||
|
||||
@@ -606,6 +606,10 @@ add_executable(step111_test tests/step111_test.cpp)
|
||||
target_include_directories(step111_test PRIVATE src)
|
||||
target_link_libraries(step111_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
add_executable(step112_test tests/step112_test.cpp)
|
||||
target_include_directories(step112_test PRIVATE src)
|
||||
target_link_libraries(step112_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
find_package(SDL2 CONFIG REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(glad CONFIG REQUIRED)
|
||||
|
||||
75
editor/src/Breadcrumbs.h
Normal file
75
editor/src/Breadcrumbs.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
// Step 112: Breadcrumb navigation helpers
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include "ast/ASTNode.h"
|
||||
#include "ast/Module.h"
|
||||
#include "ast/Function.h"
|
||||
#include "ast/Variable.h"
|
||||
#include "ast/Parameter.h"
|
||||
|
||||
struct BreadcrumbItem {
|
||||
std::string label;
|
||||
const ASTNode* node = nullptr;
|
||||
bool isRole = false;
|
||||
};
|
||||
|
||||
inline std::string breadcrumbNodeLabel(const ASTNode* node) {
|
||||
if (!node) return "";
|
||||
if (node->conceptType == "Function") {
|
||||
auto* fn = static_cast<const Function*>(node);
|
||||
return std::string("Function:") + fn->name;
|
||||
}
|
||||
if (node->conceptType == "Variable") {
|
||||
auto* var = static_cast<const Variable*>(node);
|
||||
return std::string("Variable:") + var->name;
|
||||
}
|
||||
if (node->conceptType == "Parameter") {
|
||||
auto* param = static_cast<const Parameter*>(node);
|
||||
return std::string("Parameter:") + param->name;
|
||||
}
|
||||
return node->conceptType;
|
||||
}
|
||||
|
||||
inline std::string breadcrumbRoleLabel(const ASTNode* parent, const ASTNode* child) {
|
||||
if (!parent || !child) return "";
|
||||
for (const auto& role : parent->childRoles()) {
|
||||
const auto& kids = parent->getChildren(role);
|
||||
for (auto* k : kids) {
|
||||
if (k == child) return role;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
inline std::vector<BreadcrumbItem> buildBreadcrumbTrail(const ASTNode* node) {
|
||||
std::vector<const ASTNode*> chain;
|
||||
const ASTNode* cur = node;
|
||||
while (cur) {
|
||||
chain.push_back(cur);
|
||||
cur = cur->parent;
|
||||
}
|
||||
std::reverse(chain.begin(), chain.end());
|
||||
|
||||
std::vector<BreadcrumbItem> items;
|
||||
for (size_t i = 0; i < chain.size(); ++i) {
|
||||
BreadcrumbItem item;
|
||||
item.label = breadcrumbNodeLabel(chain[i]);
|
||||
item.node = chain[i];
|
||||
items.push_back(std::move(item));
|
||||
|
||||
if (i + 1 < chain.size()) {
|
||||
std::string role = breadcrumbRoleLabel(chain[i], chain[i + 1]);
|
||||
if (!role.empty()) {
|
||||
BreadcrumbItem roleItem;
|
||||
roleItem.label = role;
|
||||
roleItem.node = nullptr;
|
||||
roleItem.isRole = true;
|
||||
items.push_back(std::move(roleItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "RefactorActions.h"
|
||||
#include "CommandPalette.h"
|
||||
#include "ContextAPI.h"
|
||||
#include "Breadcrumbs.h"
|
||||
#include "ast/Serialization.h"
|
||||
#include "ast/Generator.h"
|
||||
#include "ast/Annotation.h"
|
||||
@@ -2140,6 +2141,45 @@ int main(int, char**) {
|
||||
// Editor (center) — editable text area
|
||||
// ---------------------------------------------------------------
|
||||
ImGui::Begin("Editor");
|
||||
ImGui::PushFont(uiFont);
|
||||
ImGui::BeginChild("##breadcrumbs", ImVec2(0, 26.0f), false, ImGuiWindowFlags_NoScrollbar);
|
||||
if (!state.active()) {
|
||||
ImGui::TextDisabled("(no file)");
|
||||
} else if (!state.isStructured()) {
|
||||
ImGui::TextDisabled("Text mode");
|
||||
} else {
|
||||
Module* ast = state.activeAST();
|
||||
int lineZero = std::max(0, state.active()->cursorLine - 1);
|
||||
int colZero = std::max(0, state.active()->cursorCol - 1);
|
||||
ASTNode* node = ast ? findNodeAtPosition(ast, lineZero, colZero) : nullptr;
|
||||
if (!node) {
|
||||
ImGui::TextDisabled("(no scope)");
|
||||
} else {
|
||||
auto crumbs = buildBreadcrumbTrail(node);
|
||||
for (size_t i = 0; i < crumbs.size(); ++i) {
|
||||
if (i > 0) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(">");
|
||||
ImGui::SameLine();
|
||||
}
|
||||
ImGui::PushID((int)i);
|
||||
const auto& item = crumbs[i];
|
||||
if (item.isRole) {
|
||||
ImGui::TextDisabled("%s", item.label.c_str());
|
||||
} else if (item.node && item.node->hasSpan()) {
|
||||
if (ImGui::SmallButton(item.label.c_str())) {
|
||||
state.jumpTo(state.active(), item.node->spanStartLine, item.node->spanStartCol);
|
||||
}
|
||||
} else {
|
||||
ImGui::TextUnformatted(item.label.c_str());
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PushFont(monoFont);
|
||||
|
||||
// Tab bar for the file
|
||||
|
||||
38
editor/tests/step112_test.cpp
Normal file
38
editor/tests/step112_test.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
// Step 112 TDD Test: Breadcrumb trail
|
||||
#include "Breadcrumbs.h"
|
||||
#include "ast/Statement.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;
|
||||
|
||||
Module mod("m1", "Main", "python");
|
||||
Function fn("f1", "add");
|
||||
Return ret;
|
||||
|
||||
mod.addChild("functions", &fn);
|
||||
fn.addChild("body", &ret);
|
||||
|
||||
auto trail = buildBreadcrumbTrail(&ret);
|
||||
bool ok = trail.size() == 5 &&
|
||||
trail[0].label == "Module" &&
|
||||
trail[1].label == "functions" && trail[1].isRole &&
|
||||
trail[2].label == "Function:add" &&
|
||||
trail[3].label == "body" && trail[3].isRole &&
|
||||
trail[4].label == "Return";
|
||||
expect(ok, "breadcrumb path with roles", passed, failed);
|
||||
|
||||
std::cout << "\n=== Step 112 Results: " << passed << " passed, " << failed << " failed ===\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -327,7 +327,7 @@ Whetstone-specific AST navigation.
|
||||
Click to navigate. Icons by symbol kind. Search/filter box.
|
||||
*Wires:* `ContextAPI.h`, `LSPClient.h`
|
||||
|
||||
- [ ] **Step 112: Breadcrumb navigation**
|
||||
- [x] **Step 112: Breadcrumb navigation**
|
||||
Bar above the editor showing the current scope path:
|
||||
`Module > Function:add > body > Return`. Click any segment to jump.
|
||||
Updates as cursor moves. Uses Whetstone AST parent chain (our unique feature —
|
||||
|
||||
Reference in New Issue
Block a user