Step 94: Whetstone diagnostics layer
This commit is contained in:
@@ -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. |
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
52
editor/src/Diagnostics.h
Normal 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;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
32
editor/tests/step94_test.cpp
Normal file
32
editor/tests/step94_test.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user