Step 94: Whetstone diagnostics layer

This commit is contained in:
Bill
2026-02-09 10:05:58 -07:00
parent ede4166af3
commit 360a9fa26a
6 changed files with 170 additions and 1 deletions

View File

@@ -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. |

View File

@@ -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)

View File

@@ -21,6 +21,8 @@ struct CodeEditorOptions {
EditorMode* mode = nullptr;
bool enableFolding = false;
bool showMinimap = false;
const std::vector<int>* errorLines = nullptr;
const std::vector<int>* 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<int>* 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;

52
editor/src/Diagnostics.h Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
// Step 94: Whetstone diagnostics aggregation
//
// Converts annotation/strategy diagnostics into editor-friendly diagnostics.
#include <string>
#include <vector>
#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<EditorDiagnostic> collectWhetstoneDiagnostics(
const std::vector<AnnotationValidator::Diagnostic>& annos,
const std::vector<StrategyValidator::Violation>& violations,
const std::string& uri) {
std::vector<EditorDiagnostic> 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;
}

View File

@@ -25,6 +25,8 @@
#include "DragDropHandler.h"
#include "FileWatcher.h"
#include "LSPClient.h"
#include "Pipeline.h"
#include "Diagnostics.h"
#include "ast/Generator.h"
#include <cstdio>
@@ -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<EditorDiagnostic> whetstoneDiagnostics;
BufferState* active() { return activeBuffer; }
@@ -1003,11 +1009,29 @@ int main(int, char**) {
avail.y -= 4; // small margin
state.updateHighlights();
std::vector<int> errorLines;
std::vector<int> 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<LSPClient::Diagnostic>{};
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();

View File

@@ -0,0 +1,32 @@
// Step 94 TDD Test: Whetstone diagnostics aggregation
//
// Tests:
// 1. Whetstone diagnostics are tagged and mapped to editor diagnostics
#include <cassert>
#include <iostream>
#include "Diagnostics.h"
int main() {
int passed = 0;
int failed = 0;
std::vector<AnnotationValidator::Diagnostic> annos = {
{"error", "Missing Intent", "node1"}
};
std::vector<StrategyValidator::Violation> 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;
}