diff --git a/PROGRESS.md b/PROGRESS.md index c5b6449..4a6278d 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -193,6 +193,7 @@ All 38 steps implemented and passing. Each step has a corresponding test (`step1 - [x] Step 91: **IMPLEMENTED** — LSP diagnostics: didOpen/didChange/didSave notifications, publishDiagnostics parsing/storage, Problems panel UI (1/1 tests pass) - [x] Step 92: **IMPLEMENTED** — LSP completion requests, response parsing, and completion popup with filtering/acceptance (1/1 tests pass) - [x] Step 93: **IMPLEMENTED** — LSP hover and signature help requests, response parsing, and inline tooltip/popup (2/2 tests pass) +- [x] Step 94: **IMPLEMENTED** — Whetstone diagnostics aggregation via Pipeline, merged Problems panel, and gutter markers (1/1 tests pass) --- @@ -265,6 +266,7 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi **Step 91:** Compile and pass (1/1) **Step 92:** Compile and pass (1/1) **Step 93:** Compile and pass (2/2) +**Step 94:** Compile and pass (1/1) --- @@ -366,3 +368,4 @@ Sprint 4 in progress. Step 76 (LayoutManager) done. Next: Step 77 (custom code e | 2026-02-09 | Codex | Step 91: LSP diagnostics integration: didOpen/didChange/didSave notifications, publishDiagnostics parsing, Problems panel with jump-to-location. 1/1 tests pass. | | 2026-02-09 | Codex | Step 92: LSP completion requests/response parsing; completion popup with filtering and acceptance. 1/1 tests pass. | | 2026-02-09 | Codex | Step 93: LSP hover and signature help requests/response parsing; tooltip and signature popup. 2/2 tests pass. | +| 2026-02-09 | Codex | Step 94: Whetstone diagnostics aggregation via Pipeline; merged Problems panel and gutter markers. 1/1 tests pass. | diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 27e624c..72b9536 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -518,6 +518,10 @@ add_executable(step93_test tests/step93_test.cpp) target_include_directories(step93_test PRIVATE src) target_link_libraries(step93_test PRIVATE nlohmann_json::nlohmann_json) +add_executable(step94_test tests/step94_test.cpp) +target_include_directories(step94_test PRIVATE src) +target_link_libraries(step94_test PRIVATE nlohmann_json::nlohmann_json) + find_package(SDL2 CONFIG REQUIRED) find_package(OpenGL REQUIRED) find_package(glad CONFIG REQUIRED) diff --git a/editor/src/CodeEditorWidget.h b/editor/src/CodeEditorWidget.h index 3a2a829..2d1299a 100644 --- a/editor/src/CodeEditorWidget.h +++ b/editor/src/CodeEditorWidget.h @@ -21,6 +21,8 @@ struct CodeEditorOptions { EditorMode* mode = nullptr; bool enableFolding = false; bool showMinimap = false; + const std::vector* errorLines = nullptr; + const std::vector* warningLines = nullptr; }; struct CodeEditorResult { @@ -214,6 +216,15 @@ public: ImVec2 numPos(origin.x + gutterWidth - 4.0f - numSize.x, y); drawList->AddText(font, font->FontSize, numPos, IM_COL32(120, 120, 120, 255), numBuf); + // Diagnostics marker + bool hasError = lineIn(options.errorLines, ln); + bool hasWarn = lineIn(options.warningLines, ln); + if (hasError || hasWarn) { + ImU32 color = hasError ? IM_COL32(220, 80, 80, 255) : IM_COL32(220, 160, 60, 255); + ImVec2 center(origin.x + 3.0f, y + lineHeight * 0.5f); + drawList->AddCircleFilled(center, 3.0f, color); + } + // Fold indicator const FoldRegion* fold = findFoldAtLine(ln); if (fold) { @@ -453,6 +464,11 @@ private: } } + static bool lineIn(const std::vector* lines, int line) { + if (!lines) return false; + return std::find(lines->begin(), lines->end(), line) != lines->end(); + } + void updateFolds(const std::string& text, const std::string& language) { if (text == lastFoldText_ && language == lastFoldLang_) return; lastFoldText_ = text; diff --git a/editor/src/Diagnostics.h b/editor/src/Diagnostics.h new file mode 100644 index 0000000..044c4c1 --- /dev/null +++ b/editor/src/Diagnostics.h @@ -0,0 +1,52 @@ +#pragma once +// Step 94: Whetstone diagnostics aggregation +// +// Converts annotation/strategy diagnostics into editor-friendly diagnostics. + +#include +#include +#include "AnnotationValidator.h" +#include "StrategyValidator.h" + +struct EditorDiagnostic { + std::string uri; + int line = 0; // zero-based + int character = 0; // zero-based + int severity = 0; // 1=error, 2=warning, 3=info + std::string message; + std::string source; +}; + +inline int severityFromString(const std::string& s) { + if (s == "error") return 1; + if (s == "warning") return 2; + return 3; +} + +inline std::vector collectWhetstoneDiagnostics( + const std::vector& annos, + const std::vector& violations, + const std::string& uri) { + std::vector out; + out.reserve(annos.size() + violations.size()); + + for (const auto& d : annos) { + EditorDiagnostic ed; + ed.uri = uri; + ed.severity = severityFromString(d.severity); + ed.message = "[Whetstone] " + d.message; + ed.source = "AnnotationValidator"; + out.push_back(std::move(ed)); + } + + for (const auto& v : violations) { + EditorDiagnostic ed; + ed.uri = uri; + ed.severity = severityFromString(v.severity); + ed.message = "[Whetstone] " + v.message; + ed.source = "StrategyValidator"; + out.push_back(std::move(ed)); + } + + return out; +} diff --git a/editor/src/main.cpp b/editor/src/main.cpp index 4043b2c..5ce1e2d 100644 --- a/editor/src/main.cpp +++ b/editor/src/main.cpp @@ -25,6 +25,8 @@ #include "DragDropHandler.h" #include "FileWatcher.h" #include "LSPClient.h" +#include "Pipeline.h" +#include "Diagnostics.h" #include "ast/Generator.h" #include @@ -94,6 +96,10 @@ struct EditorState { bool hoverPending = false; double hoverLastMove = 0.0; ImVec2 hoverLastMouse = ImVec2(-1.0f, -1.0f); + Pipeline pipeline; + bool analysisPending = false; + double analysisLastChange = 0.0; + std::vector whetstoneDiagnostics; BufferState* active() { return activeBuffer; } @@ -1003,11 +1009,29 @@ int main(int, char**) { avail.y -= 4; // small margin state.updateHighlights(); + std::vector errorLines; + std::vector warningLines; + std::string activeUri = EditorState::toFileUri(buf->path); + if (state.lsp) { + auto lspDiags = state.lsp->getDiagnosticsForUri(activeUri); + for (const auto& d : lspDiags) { + if (d.severity == 1) errorLines.push_back(d.range.start.line); + else if (d.severity == 2) warningLines.push_back(d.range.start.line); + } + } + for (const auto& d : state.whetstoneDiagnostics) { + if (d.uri != activeUri) continue; + if (d.severity == 1) errorLines.push_back(d.line); + else if (d.severity == 2) warningLines.push_back(d.line); + } + CodeEditorOptions opts; opts.showWhitespace = state.showWhitespace; opts.mode = &buf->mode; opts.enableFolding = true; opts.showMinimap = state.showMinimap; + opts.errorLines = &errorLines; + opts.warningLines = &warningLines; CodeEditorResult res = buf->widget.render("##editor", buf->editBuf, buf->highlights, opts, avail, monoFont); @@ -1030,6 +1054,8 @@ int main(int, char**) { state.lsp->clearSignatureHelp(); } } + state.analysisPending = true; + state.analysisLastChange = ImGui::GetTime(); } double now = ImGui::GetTime(); @@ -1043,6 +1069,23 @@ int main(int, char**) { state.completionPending = false; } + if (state.analysisPending && (now - state.analysisLastChange) > 0.5) { + if (state.active()) { + auto result = state.pipeline.run(state.active()->editBuf, + state.active()->language, + state.active()->language); + if (result.success) { + state.whetstoneDiagnostics = + collectWhetstoneDiagnostics(result.validationDiags, + result.violations, + EditorState::toFileUri(state.active()->path)); + } else { + state.whetstoneDiagnostics.clear(); + } + } + state.analysisPending = false; + } + if (state.lsp && state.active()) { auto items = state.lsp->getCompletionItems(); int cursor = state.active()->widget.getCursor(); @@ -1177,7 +1220,8 @@ int main(int, char**) { ImGui::PushFont(monoFont); ImGui::BeginChild("##problemsScroll", ImVec2(0, 0), false); auto diags = state.lsp ? state.lsp->getDiagnostics() : std::vector{}; - if (diags.empty()) { + const auto& whetDiags = state.whetstoneDiagnostics; + if (diags.empty() && whetDiags.empty()) { ImGui::TextDisabled("(no diagnostics)"); } else { for (const auto& d : diags) { @@ -1199,6 +1243,24 @@ int main(int, char**) { } } } + for (const auto& d : whetDiags) { + const char* sev = "Unknown"; + if (d.severity == 1) sev = "Error"; + else if (d.severity == 2) sev = "Warning"; + else if (d.severity == 3) sev = "Info"; + + std::string path = EditorState::fromFileUri(d.uri); + std::string label = "[" + std::string(sev) + "] " + d.message + + " (" + path + ":" + std::to_string(d.line + 1) + + ":" + std::to_string(d.character + 1) + ")"; + if (ImGui::Selectable(label.c_str())) { + if (!path.empty()) { + if (state.buffers.hasBuffer(path)) state.switchToBuffer(path); + else state.doOpen(path); + state.jumpTo(state.active(), d.line, d.character); + } + } + } } ImGui::EndChild(); ImGui::PopFont(); diff --git a/editor/tests/step94_test.cpp b/editor/tests/step94_test.cpp new file mode 100644 index 0000000..a6dadde --- /dev/null +++ b/editor/tests/step94_test.cpp @@ -0,0 +1,32 @@ +// Step 94 TDD Test: Whetstone diagnostics aggregation +// +// Tests: +// 1. Whetstone diagnostics are tagged and mapped to editor diagnostics + +#include +#include +#include "Diagnostics.h" + +int main() { + int passed = 0; + int failed = 0; + + std::vector annos = { + {"error", "Missing Intent", "node1"} + }; + std::vector violations = { + {"warning", "leak", "Leak detected", "node2"} + }; + + auto out = collectWhetstoneDiagnostics(annos, violations, "file:///tmp/a.py"); + assert(out.size() == 2); + assert(out[0].severity == 1); + assert(out[0].message.rfind("[Whetstone]", 0) == 0); + assert(out[1].severity == 2); + assert(out[1].message.rfind("[Whetstone]", 0) == 0); + std::cout << "Test 1 PASS: Whetstone diagnostics aggregated" << std::endl; + ++passed; + + std::cout << "\n=== Step 94 Results: " << passed << " passed, " << failed << " failed ===" << std::endl; + return failed > 0 ? 1 : 0; +}