diff --git a/PROGRESS.md b/PROGRESS.md index df8b4a4..285f306 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -198,6 +198,7 @@ All 38 steps implemented and passing. Each step has a corresponding test (`step1 - [x] Step 95: **IMPLEMENTED** — Diagnostic gutter markers with tooltips and inline squiggles (1/1 tests pass) - [x] Step 96: **IMPLEMENTED** — LSP server settings UI with defaults, auto-detect, and schema validation hook (2/2 tests pass) - [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) --- @@ -275,6 +276,7 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi **Step 95:** Compile and pass (1/1) **Step 96:** Compile and pass (2/2) **Step 97:** Compile and pass (1/1) +**Step 98:** Compile and pass (1/1) --- @@ -381,3 +383,4 @@ Sprint 4 in progress. Step 76 (LayoutManager) done. Next: Step 77 (custom code e | 2026-02-09 | Codex | Step 95: Diagnostic gutter markers with tooltips and inline squiggles. 1/1 tests pass. | | 2026-02-09 | Codex | Step 96: LSP server settings UI with defaults, auto-detect, and schema validation hook. 2/2 tests pass. | | 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. | diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 73441f5..43c2174 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -538,6 +538,10 @@ add_executable(step97_test tests/step97_test.cpp) target_include_directories(step97_test PRIVATE src) target_link_libraries(step97_test PRIVATE imgui::imgui unofficial::tree-sitter::tree-sitter tree_sitter_python tree_sitter_cpp tree_sitter_elisp) +add_executable(step98_test tests/step98_test.cpp) +target_include_directories(step98_test PRIVATE src) +target_link_libraries(step98_test PRIVATE imgui::imgui unofficial::tree-sitter::tree-sitter tree_sitter_python tree_sitter_cpp tree_sitter_elisp) + 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 8d42360..162e83d 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; + bool showAnnotations = false; + int annotationLayout = 0; // 0=above, 1=margin, 2=beside const std::vector* errorLines = nullptr; const std::vector* warningLines = nullptr; const std::vector* diagnostics = nullptr; @@ -336,6 +338,27 @@ public: pos = spanEnd; } + // Inline annotation tag + if (options.showAnnotations && options.annotations) { + AnnotationMarker marker; + if (annotationAtLine(*options.annotations, ln, marker)) { + ImVec2 tagPos; + if (options.annotationLayout == 1) { + tagPos = ImVec2(origin.x + 18.0f, y); + } else if (options.annotationLayout == 2) { + tagPos = ImVec2(textBase.x + len * charAdvance + 8.0f, y); + } else { + tagPos = ImVec2(textBase.x, y - lineHeight * 0.7f); + } + ImVec2 textSize = font->CalcTextSizeA(font->FontSize, FLT_MAX, -1.0f, marker.message.c_str()); + ImVec2 bgA(tagPos.x - 2.0f, tagPos.y); + ImVec2 bgB(tagPos.x + textSize.x + 6.0f, tagPos.y + lineHeight * 0.8f); + drawList->AddRectFilled(bgA, bgB, IM_COL32(30, 30, 30, 220), 2.0f); + drawList->AddText(font, font->FontSize, ImVec2(tagPos.x + 2.0f, tagPos.y), + marker.color, marker.message.c_str()); + } + } + // Folded placeholder if (fold && fold->folded) { ImVec2 p(textBase.x + len * charAdvance + 6.0f, y); diff --git a/editor/src/main.cpp b/editor/src/main.cpp index ff6c212..feda803 100644 --- a/editor/src/main.cpp +++ b/editor/src/main.cpp @@ -28,6 +28,7 @@ #include "Pipeline.h" #include "Diagnostics.h" #include "SettingsManager.h" +#include "LayoutManager.h" #include "ast/Generator.h" #include "ast/Annotation.h" @@ -83,6 +84,8 @@ struct EditorState { // Custom editor widget state bool showWhitespace = false; bool showMinimap = false; + bool showAnnotations = false; + LayoutPreset layoutPreset = LayoutPreset::VSCode; WelcomeScreen welcome; std::string workspaceRoot; FileTree fileTree; @@ -968,7 +971,17 @@ int main(int, char**) { if (ImGui::BeginMenu("View")) { ImGui::MenuItem("Show Whitespace", nullptr, &state.showWhitespace); ImGui::MenuItem("Show Minimap", nullptr, &state.showMinimap); + ImGui::MenuItem("Show Annotations", nullptr, &state.showAnnotations); ImGui::MenuItem("LSP Servers...", nullptr, &state.showLspSettings); + if (ImGui::BeginMenu("Layout")) { + if (ImGui::MenuItem("VSCode", nullptr, state.layoutPreset == LayoutPreset::VSCode)) + state.layoutPreset = LayoutPreset::VSCode; + if (ImGui::MenuItem("Emacs", nullptr, state.layoutPreset == LayoutPreset::Emacs)) + state.layoutPreset = LayoutPreset::Emacs; + if (ImGui::MenuItem("JetBrains", nullptr, state.layoutPreset == LayoutPreset::JetBrains)) + state.layoutPreset = LayoutPreset::JetBrains; + ImGui::EndMenu(); + } ImGui::EndMenu(); } if (ImGui::BeginMenu("Language")) { @@ -1155,6 +1168,10 @@ int main(int, char**) { opts.mode = &buf->mode; opts.enableFolding = true; opts.showMinimap = state.showMinimap; + opts.showAnnotations = state.showAnnotations; + if (state.layoutPreset == LayoutPreset::Emacs) opts.annotationLayout = 1; + else if (state.layoutPreset == LayoutPreset::JetBrains) opts.annotationLayout = 2; + else opts.annotationLayout = 0; opts.errorLines = &errorLines; opts.warningLines = &warningLines; opts.diagnostics = &diagRanges; diff --git a/editor/tests/step98_test.cpp b/editor/tests/step98_test.cpp new file mode 100644 index 0000000..e27b0b4 --- /dev/null +++ b/editor/tests/step98_test.cpp @@ -0,0 +1,61 @@ +// Step 98 TDD Test: Inline annotation decorations +// +// Tests: +// 1. Render with inline annotation tags without crashing + +#include +#include +#include "imgui.h" +#include "CodeEditorWidget.h" + +static void beginFrame() { + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = ImVec2(800, 600); + io.DeltaTime = 1.0f / 60.0f; + ImGui::NewFrame(); +} + +static void endFrame() { + ImGui::Render(); +} + +int main() { + int passed = 0; + int failed = 0; + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGui::StyleColorsDark(); + + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->AddFontDefault(); + io.Fonts->Build(); + + beginFrame(); + ImGui::SetNextWindowFocus(); + ImGui::Begin("TestWindow"); + CodeEditorWidget widget; + std::string text = "def foo():\n return 1\n"; + std::vector spans; + CodeEditorOptions opts; + opts.showAnnotations = true; + opts.annotationLayout = 0; + AnnotationMarker m; + m.line = 0; + m.color = IM_COL32(80, 140, 220, 255); + m.message = "@Reclaim(Tracing)"; + std::vector markers = {m}; + opts.annotations = &markers; + + CodeEditorResult res = widget.render("##editor", text, spans, opts, ImVec2(300, 200), ImGui::GetFont()); + (void)res; + ImGui::End(); + endFrame(); + std::cout << "Test 1 PASS: Render with inline annotation tags" << std::endl; + ++passed; + + ImGui::DestroyContext(); + + std::cout << "\n=== Step 98 Results: " << passed << " passed, " << failed << " failed ===" << std::endl; + return failed > 0 ? 1 : 0; +}