Files
whetstone_DSL/editor/src/panels/EditorPanel.h
2026-02-09 22:45:42 -07:00

814 lines
51 KiB
C++

#pragma once
#include "../EditorState.h"
#include "../EditorUtils.h"
#include "../CompletionUtils.h"
#include "../AnimationUtils.h"
#include "../RichTooltip.h"
#include <map>
static void renderEditorPanel(EditorState& state) {
// ---------------------------------------------------------------
// Editor (center) -- editable text area
// ---------------------------------------------------------------
ImGui::Begin("Editor");
ImGui::PushFont(state.uiFont);
ImGui::BeginChild("##breadcrumbs", ImVec2(0, 26.0f), false, ImGuiWindowFlags_NoScrollbar);
if (!state.active()) {
ImGui::TextDisabled("(no file)");
} else if (!state.isStructured()) {
ImGui::TextDisabled("Text mode");
} else {
Module* ast = state.activeAST();
int lineZero = std::max(0, state.active()->cursorLine - 1);
int colZero = std::max(0, state.active()->cursorCol - 1);
ASTNode* node = ast ? findNodeAtPosition(ast, lineZero, colZero) : nullptr;
if (!node) {
ImGui::TextDisabled("(no scope)");
} else {
auto crumbs = buildBreadcrumbTrail(node);
for (size_t i = 0; i < crumbs.size(); ++i) {
if (i > 0) {
ImGui::SameLine();
ImGui::TextUnformatted(">");
ImGui::SameLine();
}
ImGui::PushID((int)i);
const auto& item = crumbs[i];
if (item.isRole) {
ImGui::TextDisabled("%s", item.label.c_str());
} else if (item.node && item.node->hasSpan()) {
if (ImGui::SmallButton(item.label.c_str())) {
state.jumpTo(state.active(), item.node->spanStartLine, item.node->spanStartCol);
}
} else {
ImGui::TextUnformatted(item.label.c_str());
}
ImGui::PopID();
}
}
}
ImGui::EndChild();
ImGui::PopFont();
ImGui::PushFont(state.monoFont);
// Tab bar for the file
static bool renameOpen = false;
static char renameBuf[260] = {};
static std::string renameTarget;
if (ImGui::BeginTabBar("EditorTabs",
ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs)) {
if (state.buffers.bufferCount() == 0) {
if (ImGui::BeginTabItem("Welcome")) {
RenderWelcome(state.welcome, state, state.lastDialogPath);
ImGui::EndTabItem();
}
} else {
std::map<std::string, std::pair<int, int>> diagCounts;
if (state.lsp) {
for (const auto& d : state.lsp->getDiagnostics()) {
std::string path = EditorState::fromFileUri(d.uri);
auto& counts = diagCounts[path];
if (d.severity == 1) counts.first++;
else if (d.severity == 2) counts.second++;
}
}
for (const auto& d : state.whetstoneDiagnostics) {
std::string path = EditorState::fromFileUri(d.uri);
auto& counts = diagCounts[path];
if (d.severity == 1) counts.first++;
else if (d.severity == 2) counts.second++;
}
auto openBuffers = state.buffers.getOpenBuffers();
for (const auto& path : openBuffers) {
auto* buf = state.bufferStates[path].get();
if (!buf) continue;
std::string tabLabel = path;
if (buf->modified) tabLabel += " *";
auto it = diagCounts.find(path);
if (it != diagCounts.end()) {
int err = it->second.first;
int warn = it->second.second;
if (err > 0) tabLabel += " [E" + std::to_string(err) + "]";
else if (warn > 0) tabLabel += " [W" + std::to_string(warn) + "]";
}
bool open = true;
if (ImGui::BeginTabItem(tabLabel.c_str(), &open)) {
if (!state.active() || state.active()->path != path) {
state.switchToBuffer(path);
}
const double nowSeconds = ImGui::GetTime();
float tabAlpha = state.uiAnimations.tabFadeAlpha(path,
nowSeconds,
state.settings.getReduceMotion());
ImGui::PushStyleVar(ImGuiStyleVar_Alpha,
ImGui::GetStyle().Alpha * tabAlpha);
// Editable text area fills available space
ImVec2 avail = ImGui::GetContentRegionAvail();
avail.y -= 4; // small margin
state.updateHighlights();
if (buf->bufferMode == BufferManager::BufferMode::Structured) {
state.updateGenerated();
}
std::vector<int> errorLines;
std::vector<int> warningLines;
std::vector<DiagnosticRange> diagRanges;
std::vector<AnnotationMarker> annoMarkers;
std::vector<SuggestionMarker> suggestionMarkers;
std::vector<AnnotationConflictMarker> conflictMarkers;
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);
DiagnosticRange dr;
dr.startLine = d.range.start.line;
dr.startCol = d.range.start.character;
dr.endLine = d.range.end.line;
dr.endCol = d.range.end.character;
dr.severity = d.severity;
dr.message = d.message;
diagRanges.push_back(std::move(dr));
}
}
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);
DiagnosticRange dr;
dr.startLine = d.line;
dr.startCol = d.character;
dr.endLine = d.line;
dr.endCol = d.character + 1;
dr.severity = d.severity;
dr.message = d.message;
diagRanges.push_back(std::move(dr));
}
if (state.isStructured()) {
Module* ast = state.activeAST();
if (ast) {
collectAnnotationMarkers(ast, annoMarkers);
std::vector<AnnotationConflict> conflicts;
collectAnnotationConflicts(ast, conflicts);
for (const auto& c : conflicts) {
AnnotationConflictMarker m;
m.childLine = c.childLine;
m.parentLine = c.parentLine;
m.message = c.message;
m.childAnnoId = c.childAnnoId;
m.parentAnnoId = c.parentAnnoId;
m.parentStrategy = c.parentStrategy;
conflictMarkers.push_back(std::move(m));
}
for (const auto& s : state.suggestions) {
if (s.confidence <= 0.5) continue;
ASTNode* target = findNodeById(ast, s.nodeId);
if (!target || !target->hasSpan()) continue;
SuggestionMarker sm;
sm.line = target->spanStartLine;
sm.confidence = s.confidence;
sm.reason = s.reason;
sm.annotationType = s.annotationType;
sm.strategy = s.strategy;
sm.nodeId = s.nodeId;
sm.label = "@" + s.annotationType.substr(0, s.annotationType.find("Annotation")) +
"(" + s.strategy + ")";
suggestionMarkers.push_back(std::move(sm));
}
}
}
CodeEditorResult res;
if (buf->language == "org") {
renderOrgDocument(state.emacsState.orgDoc,
buf->editBuf,
state.ui.showWhitespace,
state.monoFont,
state.uiFont,
state.settings.getLineHeightScale(),
state.settings.getLetterSpacing(),
[&](const std::string& newText) {
buf->editBuf = newText;
state.onTextChanged();
},
[&](int, const std::string&, const std::string&) {
buf->modified = true;
},
[&](const std::string& lang, const std::string& code) {
return state.runOrgBlock(lang, code);
});
res.cursorByte = buf->widget.getCursor();
} else {
CodeEditorOptions opts;
opts.showWhitespace = state.ui.showWhitespace;
opts.readOnly = buf->readOnly;
opts.mode = &buf->mode;
opts.enableFolding = true;
opts.showMinimap = state.ui.showMinimap;
opts.showAnnotations = state.ui.showAnnotations;
opts.showLineNumbers = state.ui.showLineNumbers;
opts.lineHeightScale = state.settings.getLineHeightScale();
opts.letterSpacing = state.settings.getLetterSpacing();
opts.cursorBlinkRate = state.settings.getCursorBlinkRate();
opts.reduceMotion = state.settings.getReduceMotion();
opts.nowSeconds = nowSeconds;
if (state.ui.layoutPreset == LayoutPreset::Emacs) opts.annotationLayout = 1;
else if (state.ui.layoutPreset == LayoutPreset::JetBrains) opts.annotationLayout = 2;
else opts.annotationLayout = 0;
opts.errorLines = &errorLines;
opts.warningLines = &warningLines;
opts.diagnostics = &diagRanges;
opts.annotations = &annoMarkers;
opts.suggestions = &suggestionMarkers;
opts.conflicts = &conflictMarkers;
opts.searchPulseLine = state.search.pulseLine;
opts.searchPulseStart = state.search.pulseStart;
if (buf->bufferMode == BufferManager::BufferMode::Structured) {
opts.syncScrollX = &buf->splitScrollX;
opts.syncScrollY = &buf->splitScrollY;
opts.scrollMaster = true;
ImGui::BeginTable("##editorSplit", 2,
ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchProp);
ImGui::TableSetupColumn("Source", ImGuiTableColumnFlags_WidthStretch, 0.55f);
ImGui::TableSetupColumn("Generated", ImGuiTableColumnFlags_WidthStretch, 0.45f);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
res = buf->widget.render("##editor",
buf->editBuf, buf->highlights, opts, ImVec2(0, avail.y), state.monoFont);
ImGui::TableSetColumnIndex(1);
ImGui::BeginGroup();
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted("Generated");
ImGui::SameLine();
const char* targetLabels[] = {"Python", "C++", "Elisp"};
const char* targetValues[] = {"python", "cpp", "elisp"};
int targetIndex = 0;
for (int i = 0; i < IM_ARRAYSIZE(targetValues); ++i) {
if (buf->generatedLanguage == targetValues[i]) {
targetIndex = i;
break;
}
}
ImGui::SetNextItemWidth(120.0f);
if (ImGui::Combo("##targetLang", &targetIndex,
targetLabels, IM_ARRAYSIZE(targetLabels))) {
buf->generatedLanguage = targetValues[targetIndex];
buf->generatedMode.setLanguage(buf->generatedLanguage);
buf->generatedHighlightsDirty = true;
state.updateGenerated();
}
ImVec2 genAvail = ImGui::GetContentRegionAvail();
CodeEditorOptions genOpts;
genOpts.readOnly = true;
genOpts.showWhitespace = state.ui.showWhitespace;
genOpts.mode = &buf->generatedMode;
genOpts.showLineNumbers = state.ui.showLineNumbers;
genOpts.showCurrentLine = false;
genOpts.lineHeightScale = state.settings.getLineHeightScale();
genOpts.letterSpacing = state.settings.getLetterSpacing();
genOpts.cursorBlinkRate = state.settings.getCursorBlinkRate();
genOpts.reduceMotion = state.settings.getReduceMotion();
genOpts.nowSeconds = nowSeconds;
genOpts.highlightLine = buf->generatedHighlightLine;
genOpts.syncScrollX = &buf->splitScrollX;
genOpts.syncScrollY = &buf->splitScrollY;
genOpts.scrollMaster = false;
buf->generatedWidget.render("##generated",
buf->generatedBuf, buf->generatedHighlights, genOpts,
ImVec2(0, genAvail.y), state.monoFont);
ImGui::EndGroup();
ImGui::EndTable();
} else {
res = buf->widget.render("##editor",
buf->editBuf, buf->highlights, opts, avail, state.monoFont);
}
}
if (buf->language != "org") {
state.updateCursorPos(res.cursorByte);
if (res.ctrlClick && state.active()) {
state.goToDefinitionAt(res.ctrlClickLine, res.ctrlClickCol);
}
if (res.lineClicked && buf->bufferMode == BufferManager::BufferMode::Structured) {
Module* ast = state.activeAST();
if (ast) {
int genLines = countLines(buf->generatedBuf);
int targetLine = std::max(0, std::min(res.clickedLine, genLines - 1));
buf->generatedHighlightLine = targetLine;
} else {
buf->generatedHighlightLine = -1;
}
}
if (res.changed) {
state.onTextChanged();
state.completionPending = true;
state.completionLastChange = ImGui::GetTime();
state.completionVisible = false;
state.completionSelected = 0;
if (state.lsp) state.lsp->clearCompletionItems();
if (state.lsp && state.active() && state.active()->path.rfind("(untitled", 0) != 0) {
int cursor = state.active()->widget.getCursor();
if (cursor > 0 && state.active()->editBuf[cursor - 1] == '(') {
int lineZero = std::max(0, state.active()->cursorLine - 1);
int colZero = std::max(0, state.active()->cursorCol - 1);
state.lsp->requestSignatureHelp(EditorState::toFileUri(state.active()->path),
lineZero, colZero);
} else if (cursor > 0 && state.active()->editBuf[cursor - 1] == ')') {
state.lsp->clearSignatureHelp();
}
}
if (buf->bufferMode == BufferManager::BufferMode::Structured) {
state.analysisPending = true;
state.analysisLastChange = ImGui::GetTime();
}
}
}
if (res.suggestionClicked) {
state.suggestionPopup.annotationType = res.clickedSuggestion.annotationType;
state.suggestionPopup.strategy = res.clickedSuggestion.strategy;
state.suggestionPopup.reason = res.clickedSuggestion.reason;
state.suggestionPopup.confidence = res.clickedSuggestion.confidence;
state.suggestionPopup.nodeId = res.clickedSuggestion.nodeId;
state.showSuggestionPopup = true;
ImGui::OpenPopup("SuggestionPopup");
}
double now = ImGui::GetTime();
if (state.completionPending && (now - state.completionLastChange) > 0.2) {
if (state.lsp && state.active() && state.active()->path.rfind("(untitled", 0) != 0) {
int lineZero = std::max(0, state.active()->cursorLine - 1);
int colZero = std::max(0, state.active()->cursorCol - 1);
state.lsp->requestCompletion(EditorState::toFileUri(state.active()->path),
lineZero, colZero);
}
state.completionPending = false;
}
if (state.symbolsPending && (now - state.symbolsLastChange) > 0.3) {
if (state.lsp && state.active() && state.active()->path.rfind("(untitled", 0) != 0) {
state.lsp->requestDocumentSymbols(EditorState::toFileUri(state.active()->path));
}
state.symbolsPending = false;
}
if (state.analysisPending && (now - state.analysisLastChange) > 0.5) {
if (state.isStructured()) {
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));
state.appendUnusedImportDiagnostics(state.whetstoneDiagnostics);
} else {
state.whetstoneDiagnostics.clear();
}
Module* ast = state.activeAST();
if (ast) {
MemoryStrategyInference inf;
state.suggestions = inf.inferAnnotations(ast);
} else {
state.suggestions.clear();
}
} else {
state.whetstoneDiagnostics.clear();
state.suggestions.clear();
}
state.analysisPending = false;
}
if (state.lsp && state.active()) {
auto items = state.lsp->getCompletionItems();
int cursor = state.active()->widget.getCursor();
std::string prefix = EditorState::wordPrefixAt(state.active()->editBuf, cursor);
std::string nodeId;
if (state.activeAST()) {
ASTNode* scopeNode = findNodeAtPosition(state.activeAST(),
std::max(0, state.active()->cursorLine - 1),
std::max(0, state.active()->cursorCol - 1));
if (scopeNode) nodeId = scopeNode->id;
}
std::vector<PrimitiveSymbol> primitives;
auto funcs = state.library.primitives.getAvailableFunctions(nodeId);
auto types = state.library.primitives.getAvailableTypes(nodeId);
auto consts = state.library.primitives.getAvailableConstants(nodeId);
primitives.insert(primitives.end(), funcs.begin(), funcs.end());
primitives.insert(primitives.end(), types.begin(), types.end());
primitives.insert(primitives.end(), consts.begin(), consts.end());
auto built = buildLibraryAwareCompletions(items, primitives, prefix);
std::vector<LSPClient::CompletionItem> filtered;
filtered.reserve(built.items.size());
for (const auto& item : built.items) {
const std::string& key = item.filterText.empty() ? item.label : item.filterText;
if (prefix.empty() || key.rfind(prefix, 0) == 0) filtered.push_back(item);
}
state.completionVisible = !filtered.empty();
if (state.completionVisible) {
ImGui::SetCursorScreenPos(ImVec2(ImGui::GetWindowPos().x + 20.0f,
ImGui::GetWindowPos().y + 60.0f));
ImGui::BeginChild("##completionPopup", ImVec2(300, 150), true);
int maxIndex = std::max(0, (int)filtered.size() - 1);
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
state.completionSelected = std::min(state.completionSelected + 1, maxIndex);
}
if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) {
state.completionSelected = std::max(state.completionSelected - 1, 0);
}
bool accept = ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_Tab);
bool dismiss = ImGui::IsKeyPressed(ImGuiKey_Escape);
if (dismiss) {
state.lsp->clearCompletionItems();
state.completionVisible = false;
}
auto deriveLibrary = [](const std::string& name) {
auto pos = name.find("::");
if (pos != std::string::npos) return name.substr(0, pos);
pos = name.find('.');
if (pos != std::string::npos) return name.substr(0, pos);
return std::string();
};
for (int i = 0; i < (int)filtered.size(); ++i) {
std::string itemLabel = filtered[i].label;
if (filtered[i].kind != 0) {
itemLabel = "[" + std::to_string(filtered[i].kind) + "] " + itemLabel;
}
bool selected = (i == state.completionSelected);
bool preferred = built.preferredNames.count(filtered[i].label) > 0;
std::string libHint = deriveLibrary(filtered[i].label);
if (!preferred && !libHint.empty()) itemLabel += " (auto-import)";
if (!preferred) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.6f, 0.6f, 0.6f, 1.0f));
}
if (ImGui::Selectable(itemLabel.c_str(), selected)) {
state.completionSelected = i;
accept = true;
}
if (!preferred) {
ImGui::PopStyleColor();
}
}
if (accept && !filtered.empty()) {
const auto& item = filtered[state.completionSelected];
bool preferred = built.preferredNames.count(item.label) > 0;
if (!preferred) {
std::string libHint = deriveLibrary(item.label);
if (!libHint.empty()) {
state.ensureImportForSymbol(libHint, item.label);
}
}
int start = EditorState::wordStartAt(state.active()->editBuf, cursor);
state.applyCompletion(state.active(), item.insertText, start, cursor);
state.lsp->clearCompletionItems();
state.completionVisible = false;
}
ImGui::EndChild();
}
}
if (state.showSuggestionPopup) {
if (ImGui::BeginPopupModal("SuggestionPopup", &state.showSuggestionPopup, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("%s(%s)", state.suggestionPopup.annotationType.c_str(),
state.suggestionPopup.strategy.c_str());
ImGui::Separator();
ImGui::TextWrapped("%s", state.suggestionPopup.reason.c_str());
ImGui::Text("Confidence: %.2f", state.suggestionPopup.confidence);
if (ImGui::Button("Apply")) {
Module* ast = state.mutationAST();
if (ast) {
ASTNode* target = findNodeById(ast, state.suggestionPopup.nodeId);
if (target) {
auto* anno = createAnnotationNode(state.suggestionPopup.annotationType,
state.suggestionPopup.strategy);
if (anno) {
if (target->hasSpan()) {
anno->setSpan(target->spanStartLine, target->spanStartCol,
target->spanEndLine, target->spanEndCol);
}
state.mutator.setRoot(ast);
auto res2 = state.mutator.insertNode(target->id, "annotations", anno);
if (!res2.error.empty()) state.notify(NotificationLevel::Error, res2.error);
state.applyOrchestratorToActive();
}
}
}
state.showSuggestionPopup = false;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
state.showSuggestionPopup = false;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
if (state.lsp && state.active()) {
if (ImGui::IsWindowHovered()) {
ImVec2 mouse = ImGui::GetMousePos();
if (mouse.x != state.hoverLastMouse.x || mouse.y != state.hoverLastMouse.y) {
state.hoverLastMouse = mouse;
state.hoverLastMove = ImGui::GetTime();
state.hoverPending = true;
state.lsp->clearHover();
}
if (state.hoverPending && (ImGui::GetTime() - state.hoverLastMove) > 0.5) {
if (state.active()->path.rfind("(untitled", 0) != 0) {
int lineZero = std::max(0, state.active()->cursorLine - 1);
int colZero = std::max(0, state.active()->cursorCol - 1);
state.lsp->requestHover(EditorState::toFileUri(state.active()->path),
lineZero, colZero);
}
state.hoverPending = false;
}
std::string hover = state.lsp->getHoverContents();
static std::string lastHoverText;
bool hoverActive = !hover.empty();
if (hoverActive) lastHoverText = hover;
if (!lastHoverText.empty()) {
renderRichTooltip("lsp_hover",
lastHoverText,
hoverActive,
state.settings.getReduceMotion(),
state.monoFont);
}
}
if (state.definitionPending) {
auto defs = state.lsp->getDefinitionLocations();
if (!defs.empty()) {
state.definitionPending = false;
state.definitionPreview = defs.front();
state.definitionPreviewValid = true;
state.lsp->clearDefinitionLocations();
state.jumpToDefinitionLocation(state.definitionPreview);
} else if ((ImGui::GetTime() - state.definitionLastRequest) > 0.5) {
state.definitionPending = false;
state.goToDefinitionFallback(state.definitionRequestLine,
state.definitionRequestCol);
}
}
auto sig = state.lsp->getSignatureHelp();
if (!sig.label.empty()) {
ImGui::SetCursorScreenPos(ImVec2(ImGui::GetWindowPos().x + 20.0f,
ImGui::GetWindowPos().y + ImGui::GetWindowHeight() - 80.0f));
ImGui::BeginChild("##signatureHelp", ImVec2(420, 60), true);
ImGui::TextUnformatted(sig.label.c_str());
ImGui::EndChild();
}
}
if (res.hoverValid && (ImGui::GetIO().KeyCtrl || ImGui::GetIO().KeySuper)) {
std::string preview;
if (state.previewDefinitionAt(res.hoverLine, res.hoverCol, preview)) {
renderRichTooltip("definition_preview",
preview,
true,
state.settings.getReduceMotion(),
state.monoFont);
} else if (state.definitionPreviewValid) {
std::string path = EditorState::fromFileUri(state.definitionPreview.uri);
std::string label = path + ":" +
std::to_string(state.definitionPreview.range.start.line + 1);
renderRichTooltip("definition_preview_fallback",
label,
true,
state.settings.getReduceMotion(),
state.monoFont);
}
}
if (ImGui::BeginPopupContextWindow("AnnotationContext", ImGuiPopupFlags_MouseButtonRight)) {
Module* ast = state.mutationAST();
int lineZero = state.active() ? std::max(0, state.active()->cursorLine - 1) : 0;
ASTNode* target = ast ? findAnnotationTarget(ast, lineZero) : nullptr;
if (!target) {
ImGui::TextDisabled("No annotatable symbol on this line.");
} else {
bool mutated = false;
ImGui::Text("Target: %s", nodeDisplayName(target).c_str());
ImGui::Separator();
if (ImGui::BeginMenu("Add Annotation")) {
if (ImGui::MenuItem("@Reclaim(Tracing)")) {
auto* anno = createAnnotationNode("ReclaimAnnotation", "Tracing");
if (anno) {
anno->setSpan(target->spanStartLine, target->spanStartCol,
target->spanEndLine, target->spanEndCol);
state.mutator.setRoot(ast);
auto res = state.mutator.insertNode(target->id, "annotations", anno);
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
if (res.error.empty()) mutated = true;
}
}
if (ImGui::MenuItem("@Owner(Single)")) {
auto* anno = createAnnotationNode("OwnerAnnotation", "Single");
if (anno) {
anno->setSpan(target->spanStartLine, target->spanStartCol,
target->spanEndLine, target->spanEndCol);
state.mutator.setRoot(ast);
auto res = state.mutator.insertNode(target->id, "annotations", anno);
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
if (res.error.empty()) mutated = true;
}
}
if (ImGui::MenuItem("@Deallocate(Explicit)")) {
auto* anno = createAnnotationNode("DeallocateAnnotation", "Explicit");
if (anno) {
anno->setSpan(target->spanStartLine, target->spanStartCol,
target->spanEndLine, target->spanEndCol);
state.mutator.setRoot(ast);
auto res = state.mutator.insertNode(target->id, "annotations", anno);
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
if (res.error.empty()) mutated = true;
}
}
if (ImGui::MenuItem("@Lifetime(RAII)")) {
auto* anno = createAnnotationNode("LifetimeAnnotation", "RAII");
if (anno) {
anno->setSpan(target->spanStartLine, target->spanStartCol,
target->spanEndLine, target->spanEndCol);
state.mutator.setRoot(ast);
auto res = state.mutator.insertNode(target->id, "annotations", anno);
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
if (res.error.empty()) mutated = true;
}
}
if (ImGui::MenuItem("@Allocate(Static)")) {
auto* anno = createAnnotationNode("AllocateAnnotation", "Static");
if (anno) {
anno->setSpan(target->spanStartLine, target->spanStartCol,
target->spanEndLine, target->spanEndCol);
state.mutator.setRoot(ast);
auto res = state.mutator.insertNode(target->id, "annotations", anno);
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
if (res.error.empty()) mutated = true;
}
}
ImGui::EndMenu();
}
const auto& annos = target->getChildren("annotations");
if (ImGui::BeginMenu("Edit Annotation", !annos.empty())) {
for (auto* anno : annos) {
std::string label = annotationLabel(anno);
if (ImGui::BeginMenu(label.c_str())) {
if (anno->conceptType == "ReclaimAnnotation") {
if (ImGui::MenuItem("Tracing")) {
state.mutator.setRoot(ast);
auto res = state.mutator.setProperty(anno->id, "strategy", "Tracing");
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
if (res.error.empty()) mutated = true;
}
} else if (anno->conceptType == "OwnerAnnotation") {
if (ImGui::MenuItem("Single")) {
state.mutator.setRoot(ast);
auto res = state.mutator.setProperty(anno->id, "strategy", "Single");
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
if (res.error.empty()) mutated = true;
}
} else if (anno->conceptType == "DeallocateAnnotation") {
if (ImGui::MenuItem("Explicit")) {
state.mutator.setRoot(ast);
auto res = state.mutator.setProperty(anno->id, "strategy", "Explicit");
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
if (res.error.empty()) mutated = true;
}
} else if (anno->conceptType == "LifetimeAnnotation") {
if (ImGui::MenuItem("RAII")) {
state.mutator.setRoot(ast);
auto res = state.mutator.setProperty(anno->id, "strategy", "RAII");
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
if (res.error.empty()) mutated = true;
}
} else if (anno->conceptType == "AllocateAnnotation") {
if (ImGui::MenuItem("Static")) {
state.mutator.setRoot(ast);
auto res = state.mutator.setProperty(anno->id, "strategy", "Static");
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
if (res.error.empty()) mutated = true;
}
}
ImGui::EndMenu();
}
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Remove Annotation", !annos.empty())) {
for (auto* anno : annos) {
std::string label = annotationLabel(anno);
if (ImGui::MenuItem(label.c_str())) {
state.mutator.setRoot(ast);
auto res = state.mutator.deleteNode(anno->id);
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
if (res.error.empty()) mutated = true;
}
}
ImGui::EndMenu();
}
std::vector<AnnotationConflict> conflicts;
collectAnnotationConflicts(ast, conflicts);
bool conflictOnLine = false;
AnnotationConflict lineConflict;
for (const auto& c : conflicts) {
if (c.childLine == lineZero || c.parentLine == lineZero) {
lineConflict = c;
conflictOnLine = true;
break;
}
}
if (ImGui::BeginMenu("Resolve Conflict", conflictOnLine)) {
if (conflictOnLine) {
if (ImGui::MenuItem("Remove child annotation")) {
state.mutator.setRoot(ast);
auto res = state.mutator.deleteNode(lineConflict.childAnnoId);
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
if (res.error.empty()) mutated = true;
}
if (ImGui::MenuItem("Match parent strategy")) {
state.mutator.setRoot(ast);
auto res = state.mutator.setProperty(lineConflict.childAnnoId,
"strategy",
lineConflict.parentStrategy);
if (!res.error.empty()) state.notify(NotificationLevel::Error, res.error);
if (res.error.empty()) mutated = true;
}
}
ImGui::EndMenu();
}
if (mutated) {
state.applyOrchestratorToActive();
}
}
ImGui::EndPopup();
}
ImGui::PopStyleVar();
ImGui::EndTabItem();
}
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
if (path.rfind("(untitled", 0) == 0) {
renameOpen = true;
renameTarget = path;
std::snprintf(renameBuf, sizeof(renameBuf), "%s", path.c_str());
ImGui::OpenPopup("Rename Buffer");
}
}
if (!open) {
state.buffers.closeBuffer(path);
state.bufferStates.erase(path);
if (path.rfind("(untitled", 0) != 0) state.watcher.unwatch(path);
if (state.buffers.bufferCount() > 0) {
state.switchToBuffer(state.buffers.getOpenBuffers().front());
} else {
state.activeBuffer = nullptr;
}
}
}
}
ImGui::EndTabBar();
}
if (renameOpen && ImGui::BeginPopupModal("Rename Buffer", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextUnformatted("Rename untitled buffer:");
ImGui::SetNextItemWidth(320.0f);
ImGui::InputText("##renameBuf", renameBuf, sizeof(renameBuf));
if (ImGui::Button("Rename")) {
std::string newName = renameBuf;
if (!newName.empty() && renameTarget.rfind("(untitled", 0) == 0) {
state.renameBufferPath(renameTarget, newName);
}
renameOpen = false;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
renameOpen = false;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::PopFont();
ImGui::End();
}