Step 100: memory strategy suggestions
This commit is contained in:
@@ -200,6 +200,7 @@ All 38 steps implemented and passing. Each step has a corresponding test (`step1
|
||||
- [x] Step 97: **IMPLEMENTED** — Annotation gutter markers with hover details and click-to-open placeholder editor (1/1 tests pass)
|
||||
- [x] Step 98: **IMPLEMENTED** — Inline annotation tags with layout-aware placement and view toggle (1/1 tests pass)
|
||||
- [x] Step 99: **IMPLEMENTED** — Annotation context menu for add/edit/remove via ASTMutationAPI (2/2 tests pass)
|
||||
- [x] Step 100: **IMPLEMENTED** — Memory strategy suggestions with lightbulb gutter icons and apply popup (1/1 tests pass)
|
||||
|
||||
---
|
||||
|
||||
@@ -279,6 +280,7 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi
|
||||
**Step 97:** Compile and pass (1/1)
|
||||
**Step 98:** Compile and pass (1/1)
|
||||
**Step 99:** Compile and pass (2/2)
|
||||
**Step 100:** Compile and pass (1/1)
|
||||
|
||||
---
|
||||
|
||||
@@ -387,3 +389,4 @@ Sprint 4 in progress. Step 76 (LayoutManager) done. Next: Step 77 (custom code e
|
||||
| 2026-02-09 | Codex | Step 97: Annotation gutter markers with hover details and click-to-open placeholder editor. 1/1 tests pass. |
|
||||
| 2026-02-09 | Codex | Step 98: Inline annotation tags with layout-aware placement and view toggle. 1/1 tests pass. |
|
||||
| 2026-02-09 | Codex | Step 99: Annotation context menu (add/edit/remove) via ASTMutationAPI. 2/2 tests pass. |
|
||||
| 2026-02-09 | Codex | Step 100: Memory strategy suggestions with lightbulb icons and apply popup. 1/1 tests pass. |
|
||||
|
||||
@@ -546,6 +546,10 @@ add_executable(step99_test tests/step99_test.cpp)
|
||||
target_include_directories(step99_test PRIVATE src)
|
||||
target_link_libraries(step99_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
add_executable(step100_test tests/step100_test.cpp)
|
||||
target_include_directories(step100_test PRIVATE src)
|
||||
target_link_libraries(step100_test PRIVATE nlohmann_json::nlohmann_json)
|
||||
|
||||
find_package(SDL2 CONFIG REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(glad CONFIG REQUIRED)
|
||||
|
||||
@@ -27,6 +27,7 @@ struct CodeEditorOptions {
|
||||
const std::vector<int>* warningLines = nullptr;
|
||||
const std::vector<struct DiagnosticRange>* diagnostics = nullptr;
|
||||
const std::vector<struct AnnotationMarker>* annotations = nullptr;
|
||||
const std::vector<struct SuggestionMarker>* suggestions = nullptr;
|
||||
};
|
||||
|
||||
struct CodeEditorResult {
|
||||
@@ -41,6 +42,8 @@ struct CodeEditorResult {
|
||||
float minimapWidth = 0.0f;
|
||||
float minimapViewportStart = 0.0f;
|
||||
float minimapViewportEnd = 0.0f;
|
||||
bool suggestionClicked = false;
|
||||
struct SuggestionMarker clickedSuggestion;
|
||||
};
|
||||
|
||||
struct DiagnosticRange {
|
||||
@@ -58,6 +61,16 @@ struct AnnotationMarker {
|
||||
std::string message;
|
||||
};
|
||||
|
||||
struct SuggestionMarker {
|
||||
int line = 0;
|
||||
double confidence = 0.0;
|
||||
std::string label;
|
||||
std::string reason;
|
||||
std::string annotationType;
|
||||
std::string strategy;
|
||||
std::string nodeId;
|
||||
};
|
||||
|
||||
struct FoldRegion {
|
||||
int startLine = 0;
|
||||
int endLine = 0;
|
||||
@@ -275,6 +288,29 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// Suggestion marker (lightbulb)
|
||||
if (options.suggestions) {
|
||||
SuggestionMarker suggestion;
|
||||
if (suggestionAtLine(*options.suggestions, ln, suggestion)) {
|
||||
ImVec2 center(origin.x + 20.0f, y + lineHeight * 0.5f);
|
||||
ImU32 color = IM_COL32(240, 200, 40, 255);
|
||||
drawList->AddCircleFilled(center, 3.0f, color);
|
||||
ImVec2 a(center.x - 4.0f, center.y - 4.0f);
|
||||
ImVec2 b(center.x + 4.0f, center.y + 4.0f);
|
||||
if (ImGui::IsMouseHoveringRect(a, b)) {
|
||||
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
result.suggestionClicked = true;
|
||||
result.clickedSuggestion = suggestion;
|
||||
}
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::Text("%s (%.2f)", suggestion.label.c_str(), suggestion.confidence);
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted(suggestion.reason.c_str());
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fold indicator
|
||||
const FoldRegion* fold = findFoldAtLine(ln);
|
||||
if (fold) {
|
||||
@@ -575,6 +611,18 @@ private:
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool suggestionAtLine(const std::vector<SuggestionMarker>& markers,
|
||||
int line,
|
||||
SuggestionMarker& out) {
|
||||
for (const auto& m : markers) {
|
||||
if (m.line == line) {
|
||||
out = m;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::string diagnosticMessageAtLine(const std::vector<DiagnosticRange>& diags, int line) {
|
||||
for (const auto& d : diags) {
|
||||
if (line >= d.startLine && line <= d.endLine) return d.message;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "SettingsManager.h"
|
||||
#include "LayoutManager.h"
|
||||
#include "ASTMutationAPI.h"
|
||||
#include "MemoryStrategyInference.h"
|
||||
#include "ast/Generator.h"
|
||||
#include "ast/Annotation.h"
|
||||
|
||||
@@ -110,6 +111,9 @@ struct EditorState {
|
||||
SettingsManager settings;
|
||||
bool showLspSettings = false;
|
||||
ASTMutationAPI mutator;
|
||||
std::vector<MemoryStrategyInference::Suggestion> suggestions;
|
||||
bool showSuggestionPopup = false;
|
||||
MemoryStrategyInference::Suggestion suggestionPopup;
|
||||
|
||||
BufferState* active() { return activeBuffer; }
|
||||
|
||||
@@ -616,6 +620,15 @@ static ASTNode* findAnnotationTarget(ASTNode* node, int line) {
|
||||
return best;
|
||||
}
|
||||
|
||||
static ASTNode* findNodeById(ASTNode* node, const std::string& id) {
|
||||
if (!node) return nullptr;
|
||||
if (node->id == id) return node;
|
||||
for (auto* child : node->allChildren()) {
|
||||
if (auto* found = findNodeById(child, id)) return found;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void collectAnnotationMarkers(const ASTNode* node, std::vector<AnnotationMarker>& out) {
|
||||
if (!node) return;
|
||||
if (node->hasSpan()) {
|
||||
@@ -1217,6 +1230,7 @@ int main(int, char**) {
|
||||
std::vector<int> warningLines;
|
||||
std::vector<DiagnosticRange> diagRanges;
|
||||
std::vector<AnnotationMarker> annoMarkers;
|
||||
std::vector<SuggestionMarker> suggestionMarkers;
|
||||
std::string activeUri = EditorState::toFileUri(buf->path);
|
||||
if (state.lsp) {
|
||||
auto lspDiags = state.lsp->getDiagnosticsForUri(activeUri);
|
||||
@@ -1248,7 +1262,24 @@ int main(int, char**) {
|
||||
}
|
||||
if (state.active()) {
|
||||
Module* ast = state.active()->sync.getAST();
|
||||
if (ast) collectAnnotationMarkers(ast, annoMarkers);
|
||||
if (ast) {
|
||||
collectAnnotationMarkers(ast, annoMarkers);
|
||||
for (const auto& s : state.suggestions) {
|
||||
if (s.confidence <= 0.5) continue;
|
||||
ASTNode* target = findNodeById(ast, s.nodeId);
|
||||
if (!target || !target->hasSpan()) continue;
|
||||
SuggestionMarker sm;
|
||||
sm.line = target->spanStartLine;
|
||||
sm.confidence = s.confidence;
|
||||
sm.reason = s.reason;
|
||||
sm.annotationType = s.annotationType;
|
||||
sm.strategy = s.strategy;
|
||||
sm.nodeId = s.nodeId;
|
||||
sm.label = "@" + s.annotationType.substr(0, s.annotationType.find("Annotation")) +
|
||||
"(" + s.strategy + ")";
|
||||
suggestionMarkers.push_back(std::move(sm));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CodeEditorOptions opts;
|
||||
@@ -1264,6 +1295,7 @@ int main(int, char**) {
|
||||
opts.warningLines = &warningLines;
|
||||
opts.diagnostics = &diagRanges;
|
||||
opts.annotations = &annoMarkers;
|
||||
opts.suggestions = &suggestionMarkers;
|
||||
|
||||
CodeEditorResult res = buf->widget.render("##editor",
|
||||
buf->editBuf, buf->highlights, opts, avail, monoFont);
|
||||
@@ -1290,6 +1322,16 @@ int main(int, char**) {
|
||||
state.analysisLastChange = ImGui::GetTime();
|
||||
}
|
||||
|
||||
if (res.suggestionClicked) {
|
||||
state.suggestionPopup.annotationType = res.clickedSuggestion.annotationType;
|
||||
state.suggestionPopup.strategy = res.clickedSuggestion.strategy;
|
||||
state.suggestionPopup.reason = res.clickedSuggestion.reason;
|
||||
state.suggestionPopup.confidence = res.clickedSuggestion.confidence;
|
||||
state.suggestionPopup.nodeId = res.clickedSuggestion.nodeId;
|
||||
state.showSuggestionPopup = true;
|
||||
ImGui::OpenPopup("SuggestionPopup");
|
||||
}
|
||||
|
||||
double now = ImGui::GetTime();
|
||||
if (state.completionPending && (now - state.completionLastChange) > 0.2) {
|
||||
if (state.lsp && state.active() && state.active()->path.rfind("(untitled", 0) != 0) {
|
||||
@@ -1314,6 +1356,13 @@ int main(int, char**) {
|
||||
} else {
|
||||
state.whetstoneDiagnostics.clear();
|
||||
}
|
||||
Module* ast = state.active()->sync.getAST();
|
||||
if (ast) {
|
||||
MemoryStrategyInference inf;
|
||||
state.suggestions = inf.inferAnnotations(ast);
|
||||
} else {
|
||||
state.suggestions.clear();
|
||||
}
|
||||
}
|
||||
state.analysisPending = false;
|
||||
}
|
||||
@@ -1372,6 +1421,43 @@ int main(int, char**) {
|
||||
}
|
||||
}
|
||||
|
||||
if (state.showSuggestionPopup) {
|
||||
if (ImGui::BeginPopupModal("SuggestionPopup", &state.showSuggestionPopup, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text("%s(%s)", state.suggestionPopup.annotationType.c_str(),
|
||||
state.suggestionPopup.strategy.c_str());
|
||||
ImGui::Separator();
|
||||
ImGui::TextWrapped("%s", state.suggestionPopup.reason.c_str());
|
||||
ImGui::Text("Confidence: %.2f", state.suggestionPopup.confidence);
|
||||
if (ImGui::Button("Apply")) {
|
||||
Module* ast = state.active() ? state.active()->sync.getAST() : nullptr;
|
||||
if (ast) {
|
||||
ASTNode* target = findNodeById(ast, state.suggestionPopup.nodeId);
|
||||
if (target) {
|
||||
auto* anno = createAnnotationNode(state.suggestionPopup.annotationType,
|
||||
state.suggestionPopup.strategy);
|
||||
if (anno) {
|
||||
if (target->hasSpan()) {
|
||||
anno->setSpan(target->spanStartLine, target->spanStartCol,
|
||||
target->spanEndLine, target->spanEndCol);
|
||||
}
|
||||
state.mutator.setRoot(ast);
|
||||
auto res2 = state.mutator.insertNode(target->id, "annotations", anno);
|
||||
if (!res2.error.empty()) state.outputLog += res2.error + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
state.showSuggestionPopup = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel")) {
|
||||
state.showSuggestionPopup = false;
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
if (state.lsp && state.active()) {
|
||||
if (ImGui::IsWindowHovered()) {
|
||||
ImVec2 mouse = ImGui::GetMousePos();
|
||||
|
||||
31
editor/tests/step100_test.cpp
Normal file
31
editor/tests/step100_test.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
// Step 100 TDD Test: Memory strategy suggestions
|
||||
//
|
||||
// Tests:
|
||||
// 1. Inference suggests tracing for Python module
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include "MemoryStrategyInference.h"
|
||||
#include "ast/Module.h"
|
||||
|
||||
int main() {
|
||||
int passed = 0;
|
||||
int failed = 0;
|
||||
|
||||
Module mod("m1", "mod", "python");
|
||||
MemoryStrategyInference inf;
|
||||
auto suggestions = inf.inferAnnotations(&mod);
|
||||
bool found = false;
|
||||
for (const auto& s : suggestions) {
|
||||
if (s.annotationType == "ReclaimAnnotation" && s.strategy == "Tracing" && s.confidence > 0.5) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(found);
|
||||
std::cout << "Test 1 PASS: tracing suggestion for python" << std::endl;
|
||||
++passed;
|
||||
|
||||
std::cout << "\n=== Step 100 Results: " << passed << " passed, " << failed << " failed ===" << std::endl;
|
||||
return failed > 0 ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user