Step 97: annotation gutter markers

This commit is contained in:
Bill
2026-02-09 10:25:58 -07:00
parent 5fdb21d74d
commit 5cf7bf575f
5 changed files with 182 additions and 0 deletions

View File

@@ -24,6 +24,7 @@ struct CodeEditorOptions {
const std::vector<int>* errorLines = nullptr;
const std::vector<int>* warningLines = nullptr;
const std::vector<struct DiagnosticRange>* diagnostics = nullptr;
const std::vector<struct AnnotationMarker>* annotations = nullptr;
};
struct CodeEditorResult {
@@ -49,6 +50,12 @@ struct DiagnosticRange {
std::string message;
};
struct AnnotationMarker {
int line = 0;
ImU32 color = 0;
std::string message;
};
struct FoldRegion {
int startLine = 0;
int endLine = 0;
@@ -112,6 +119,7 @@ public:
lineCount * lineHeight + 4.0f);
ImGui::BeginChild(id, size, false, ImGuiWindowFlags_HorizontalScrollbar);
std::string annoPopupId = std::string(id) + "_AnnoPopup";
ImVec2 origin = ImGui::GetCursorScreenPos();
// Dummy to set scroll extents
@@ -245,6 +253,26 @@ public:
}
}
// Annotation marker
if (options.annotations) {
AnnotationMarker marker;
if (annotationAtLine(*options.annotations, ln, marker)) {
ImVec2 center(origin.x + 12.0f, y + lineHeight * 0.5f);
drawList->AddCircleFilled(center, 3.0f, marker.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)) {
annotationPopupMessage_ = marker.message;
ImGui::OpenPopup(annoPopupId.c_str());
}
ImGui::BeginTooltip();
ImGui::TextUnformatted(marker.message.c_str());
ImGui::EndTooltip();
}
}
}
// Fold indicator
const FoldRegion* fold = findFoldAtLine(ln);
if (fold) {
@@ -330,6 +358,13 @@ public:
}
}
if (ImGui::BeginPopup(annoPopupId.c_str())) {
ImGui::TextUnformatted("Annotation Editor (TODO)");
ImGui::Separator();
ImGui::TextUnformatted(annotationPopupMessage_.c_str());
ImGui::EndPopup();
}
// Cursor
if (focused) {
const double t = ImGui::GetTime();
@@ -408,6 +443,7 @@ private:
std::vector<FoldRegion> folds_;
std::string lastFoldText_;
std::string lastFoldLang_;
std::string annotationPopupMessage_;
bool hasSelection() const {
return selStart_ >= 0 && selEnd_ >= 0 && selStart_ != selEnd_;
@@ -504,6 +540,18 @@ private:
return std::find(lines->begin(), lines->end(), line) != lines->end();
}
static bool annotationAtLine(const std::vector<AnnotationMarker>& markers,
int line,
AnnotationMarker& 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;

View File

@@ -29,6 +29,7 @@
#include "Diagnostics.h"
#include "SettingsManager.h"
#include "ast/Generator.h"
#include "ast/Annotation.h"
#include <cstdio>
#include <string>
@@ -483,6 +484,67 @@ static bool InputTextStr(const char* label, std::string* str, ImGuiInputTextFlag
flags, InputTextCallback, &cbData);
}
static bool annotationInfo(const ASTNode* anno, ImU32& color, std::string& label) {
if (!anno) return false;
if (anno->conceptType == "ReclaimAnnotation") {
auto* a = static_cast<const ReclaimAnnotation*>(anno);
if (a->strategy == "Tracing") {
color = IM_COL32(80, 140, 220, 255);
label = "@Reclaim(Tracing)";
return true;
}
} else if (anno->conceptType == "OwnerAnnotation") {
auto* a = static_cast<const OwnerAnnotation*>(anno);
if (a->strategy == "Single") {
color = IM_COL32(220, 160, 60, 255);
label = "@Owner(Single)";
return true;
}
} else if (anno->conceptType == "DeallocateAnnotation") {
auto* a = static_cast<const DeallocateAnnotation*>(anno);
if (a->strategy == "Explicit") {
color = IM_COL32(220, 80, 80, 255);
label = "@Deallocate(Explicit)";
return true;
}
} else if (anno->conceptType == "LifetimeAnnotation") {
auto* a = static_cast<const LifetimeAnnotation*>(anno);
if (a->strategy == "RAII") {
color = IM_COL32(80, 180, 100, 255);
label = "@Lifetime(RAII)";
return true;
}
} else if (anno->conceptType == "AllocateAnnotation") {
auto* a = static_cast<const AllocateAnnotation*>(anno);
if (a->strategy == "Static") {
color = IM_COL32(160, 160, 160, 255);
label = "@Allocate(Static)";
return true;
}
}
return false;
}
static void collectAnnotationMarkers(const ASTNode* node, std::vector<AnnotationMarker>& out) {
if (!node) return;
if (node->hasSpan()) {
for (const auto* anno : node->getChildren("annotations")) {
ImU32 color = 0;
std::string label;
if (annotationInfo(anno, color, label)) {
AnnotationMarker marker;
marker.line = node->spanStartLine;
marker.color = color;
marker.message = label;
out.push_back(std::move(marker));
}
}
}
for (auto* child : node->allChildren()) {
collectAnnotationMarkers(child, out);
}
}
// ---------------------------------------------------------------------------
// Theme setup — VSCode Dark-inspired
// ---------------------------------------------------------------------------
@@ -1053,6 +1115,7 @@ int main(int, char**) {
std::vector<int> errorLines;
std::vector<int> warningLines;
std::vector<DiagnosticRange> diagRanges;
std::vector<AnnotationMarker> annoMarkers;
std::string activeUri = EditorState::toFileUri(buf->path);
if (state.lsp) {
auto lspDiags = state.lsp->getDiagnosticsForUri(activeUri);
@@ -1082,6 +1145,10 @@ int main(int, char**) {
dr.message = d.message;
diagRanges.push_back(std::move(dr));
}
if (state.active()) {
Module* ast = state.active()->sync.getAST();
if (ast) collectAnnotationMarkers(ast, annoMarkers);
}
CodeEditorOptions opts;
opts.showWhitespace = state.showWhitespace;
@@ -1091,6 +1158,7 @@ int main(int, char**) {
opts.errorLines = &errorLines;
opts.warningLines = &warningLines;
opts.diagnostics = &diagRanges;
opts.annotations = &annoMarkers;
CodeEditorResult res = buf->widget.render("##editor",
buf->editBuf, buf->highlights, opts, avail, monoFont);