Files
whetstone_DSL/editor/src/panels/BottomPanel.h
2026-02-09 21:26:10 -07:00

696 lines
33 KiB
C++

#pragma once
#include "../EditorState.h"
#include "../EditorUtils.h"
static void renderBottomPanel(EditorState& state) {
// ---------------------------------------------------------------
// Bottom panel — Output / AST / Highlighted Preview / Terminal
// ---------------------------------------------------------------
ImGui::Begin("Panel");
if (ImGui::BeginTabBar("PanelTabs")) {
// Output log
if (ImGui::BeginTabItem("Output")) {
ImGui::PushFont(state.monoFont);
ImGui::BeginChild("##outputScroll", ImVec2(0, 0), false);
for (const auto& note : state.notifications.getHistory()) {
const char* label = "Info";
ImVec4 color = ImVec4(0.40f, 0.70f, 0.95f, 1.0f);
switch (note.level) {
case NotificationLevel::Success:
label = "Success";
color = ImVec4(0.25f, 0.78f, 0.35f, 1.0f);
break;
case NotificationLevel::Warning:
label = "Warning";
color = ImVec4(0.95f, 0.72f, 0.28f, 1.0f);
break;
case NotificationLevel::Error:
label = "Error";
color = ImVec4(0.90f, 0.35f, 0.35f, 1.0f);
break;
case NotificationLevel::Info:
default:
break;
}
ImGui::TextColored(color, "[%s]", label);
ImGui::SameLine();
ImGui::TextWrapped("%s", note.message.c_str());
}
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - 20)
ImGui::SetScrollHereY(1.0f);
ImGui::EndChild();
ImGui::PopFont();
ImGui::EndTabItem();
}
if (state.build.showTerminalPanel && ImGui::BeginTabItem("Terminal")) {
state.build.terminal.render(state.workspaceRoot, state.monoFont);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Agents")) {
ImGui::PushFont(state.monoFont);
bool running = state.agent.server && state.agent.server->isRunning();
ImGui::Text("Server: %s", running ? "Running" : "Stopped");
ImGui::SameLine(0, 20);
ImGui::Text("Port: %d", state.agent.port);
ImGui::Separator();
ImGui::TextUnformatted("Active Sessions");
if (!state.agent.server || state.agent.server->getActiveSessionCount() == 0) {
ImGui::TextDisabled("(none)");
} else {
for (const auto& s : state.agent.server->getActiveSessions()) {
std::string label = s.sessionId;
if (!s.agentName.empty()) label += " (" + s.agentName + ")";
ImGui::TextUnformatted(label.c_str());
ImGui::SameLine(260.0f);
AgentRole role = state.getAgentRole(s.sessionId);
int roleIndex = static_cast<int>(role);
const char* roleLabels[] = {"Linter", "Refactor", "Generator"};
std::string comboId = "Role##" + s.sessionId;
if (ImGui::Combo(comboId.c_str(), &roleIndex, roleLabels, 3)) {
state.setAgentRole(s.sessionId, static_cast<AgentRole>(roleIndex));
}
ImGui::SameLine(420.0f);
ImGui::Text("Msgs: %d", s.messageCount);
ImGui::SameLine(520.0f);
ImGui::Text("Last: %llu", (unsigned long long)s.lastMessageAtMs);
ImGui::SameLine(680.0f);
std::string btnId = "Disconnect##" + s.sessionId;
if (ImGui::Button(btnId.c_str())) {
if (state.agent.transport) {
state.agent.transport->simulateDisconnect(s.sessionId);
} else {
state.logAgentEvent("STUB: disconnect not supported for real transport");
}
}
ImGui::Separator();
}
}
ImGui::Separator();
ImGui::TextUnformatted("Activity Log");
ImGui::BeginChild("##agentLog", ImVec2(0, 0), false);
for (const auto& line : state.agent.log) {
ImGui::TextUnformatted(line.c_str());
}
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - 20)
ImGui::SetScrollHereY(1.0f);
ImGui::EndChild();
ImGui::PopFont();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Marketplace")) {
ImGui::PushFont(state.monoFont);
renderAgentMarketplace(state.agent.marketplace,
state.agent.registry,
state.notifications,
state.workspaceRoot);
ImGui::PopFont();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Build")) {
ImGui::PushFont(state.uiFont);
ImGui::Text("Detected: %s", BuildSystem::typeName(state.build.buildType));
auto cmds = BuildSystem::commandsFor(state.build.buildType);
if (cmds.empty()) {
ImGui::TextDisabled("(no build system detected)");
} else {
for (const auto& cmd : cmds) {
if (ImGui::Button(cmd.label.c_str())) {
state.runBuildCommand(cmd);
}
ImGui::SameLine();
ImGui::TextUnformatted(cmd.command.c_str());
}
}
ImGui::Separator();
ImGui::TextUnformatted("Errors");
if (state.build.buildErrors.empty()) {
ImGui::TextDisabled("(none)");
} else {
for (const auto& err : state.build.buildErrors) {
std::string label = err.file + ":" + std::to_string(err.line);
if (err.col > 0) label += ":" + std::to_string(err.col);
label += " " + err.message;
if (ImGui::Selectable(label.c_str())) {
std::filesystem::path p(err.file);
std::string path = p.is_absolute()
? p.string()
: (std::filesystem::path(state.workspaceRoot) / p).string();
if (state.buffers.hasBuffer(path)) state.switchToBuffer(path);
else state.doOpen(path);
if (state.active()) {
state.jumpTo(state.active(),
std::max(0, err.line - 1),
std::max(0, err.col - 1));
}
}
}
}
ImGui::PopFont();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Help")) {
ImGui::PushFont(state.uiFont);
renderHelpPanel(state.helpPanel, state.workspaceRoot, state.monoFont);
ImGui::PopFont();
ImGui::EndTabItem();
}
// Problems (LSP diagnostics)
if (ImGui::BeginTabItem("Problems")) {
ImGui::PushFont(state.monoFont);
ImGui::BeginChild("##problemsScroll", ImVec2(0, 0), false);
auto diags = state.lsp ? state.lsp->getDiagnostics() : std::vector<LSPClient::Diagnostic>{};
const auto& whetDiags = state.whetstoneDiagnostics;
const auto& emacsDiags = state.emacsDiagnostics;
if (diags.empty() && whetDiags.empty() && emacsDiags.empty()) {
ImGui::TextDisabled("(no diagnostics)");
} else {
for (const auto& d : diags) {
const char* sev = "Unknown";
if (d.severity == 1) sev = "Error";
else if (d.severity == 2) sev = "Warning";
else if (d.severity == 3) sev = "Info";
else if (d.severity == 4) sev = "Hint";
std::string path = EditorState::fromFileUri(d.uri);
std::string label = "[" + std::string(sev) + "] " + d.message +
" (" + path + ":" + std::to_string(d.range.start.line + 1) +
":" + std::to_string(d.range.start.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.range.start.line, d.range.start.character);
}
}
}
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);
}
}
}
for (const auto& d : emacsDiags) {
const char* sev = "Error";
std::string label = "[" + std::string(sev) + "] " + d.message +
" (" + d.uri + ")";
ImGui::TextUnformatted(label.c_str());
}
}
ImGui::EndChild();
ImGui::PopFont();
ImGui::EndTabItem();
}
// Optimization controls
if (ImGui::BeginTabItem("Optimize")) {
ImGui::PushFont(state.uiFont);
Module* ast = state.mutationAST();
if (!state.active()) {
ImGui::TextDisabled("(no active buffer)");
} else if (state.active()->bufferMode == BufferManager::BufferMode::Text) {
ImGui::TextDisabled("(disabled in Text mode)");
} else if (!ast) {
ImGui::TextDisabled("(no AST)");
} else if (state.active()->readOnly) {
ImGui::TextDisabled("(read-only buffer)");
} else {
ImGui::Checkbox("Preview changes", &state.optimizePreview);
std::string blockReason = findOptimizationBlockReason(ast);
bool blocked = !blockReason.empty();
auto showBlockedTooltip = [&](const std::string& reason) {
if (!reason.empty() &&
ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
ImGui::BeginTooltip();
ImGui::TextUnformatted(reason.c_str());
ImGui::EndTooltip();
}
};
if (blocked) ImGui::BeginDisabled();
if (ImGui::Button("Constant Fold")) {
std::string beforeText = state.active()->editBuf;
if (state.optimizePreview) {
auto previewAst = cloneModule(ast);
TransformEngine engine;
engine.setRoot(previewAst.get());
engine.constantFolding();
std::string afterText = generateForLanguage(previewAst.get(),
state.active()->language);
state.openDiff(beforeText, afterText, true, 1, {});
} else {
auto& inc = state.active()->incrementalOptimizer;
inc.setRoot(ast);
std::string tid = inc.applyTransform("constant-fold");
auto history = inc.getTransformHistory();
size_t nodes = 0;
for (const auto& h : history) {
if (h.transformId == tid) {
nodes = h.affectedNodeIds.size();
break;
}
}
std::string warning = findOptimizationLockWarning(ast);
std::string summary = "Constant Fold: ";
if (nodes > 0) {
summary += "applied (" + std::to_string(nodes) + " nodes)";
} else {
summary += "no changes";
}
if (!warning.empty()) {
summary += " — warning: " + warning;
state.notify(NotificationLevel::Warning, warning);
}
state.optimizeFoldSummary = summary;
state.notify(NotificationLevel::Info, summary);
if (nodes > 0) state.applyOrchestratorToActive();
std::string afterText = state.active()->editBuf;
state.openDiff(beforeText, afterText, false, 1, {tid});
}
}
showBlockedTooltip(blockReason);
if (blocked) ImGui::EndDisabled();
if (!state.optimizeFoldSummary.empty()) {
ImGui::TextWrapped("%s", state.optimizeFoldSummary.c_str());
}
ImGui::Separator();
if (blocked) ImGui::BeginDisabled();
if (ImGui::Button("Dead Code Elimination")) {
std::string beforeText = state.active()->editBuf;
if (state.optimizePreview) {
auto previewAst = cloneModule(ast);
TransformEngine engine;
engine.setRoot(previewAst.get());
engine.deadCodeElimination();
std::string afterText = generateForLanguage(previewAst.get(),
state.active()->language);
state.openDiff(beforeText, afterText, true, 2, {});
} else {
auto& inc = state.active()->incrementalOptimizer;
inc.setRoot(ast);
std::string tid = inc.applyTransform("dead-code-elim");
auto history = inc.getTransformHistory();
size_t nodes = 0;
for (const auto& h : history) {
if (h.transformId == tid) {
nodes = h.affectedNodeIds.size();
break;
}
}
std::string summary = "Dead Code Elimination: ";
if (nodes > 0) {
summary += "applied (" + std::to_string(nodes) + " nodes)";
} else {
summary += "no changes";
}
state.optimizeDeadCodeSummary = summary;
state.notify(NotificationLevel::Info, summary);
if (nodes > 0) state.applyOrchestratorToActive();
std::string afterText = state.active()->editBuf;
state.openDiff(beforeText, afterText, false, 2, {tid});
}
}
showBlockedTooltip(blockReason);
if (blocked) ImGui::EndDisabled();
if (!state.optimizeDeadCodeSummary.empty()) {
ImGui::TextWrapped("%s", state.optimizeDeadCodeSummary.c_str());
}
ImGui::Separator();
if (blocked) ImGui::BeginDisabled();
if (ImGui::Button("Apply All")) {
std::string beforeText = state.active()->editBuf;
if (state.optimizePreview) {
auto previewAst = cloneModule(ast);
TransformEngine engine;
engine.setRoot(previewAst.get());
engine.applyAll();
std::string afterText = generateForLanguage(previewAst.get(),
state.active()->language);
state.openDiff(beforeText, afterText, true, 3, {});
} else {
auto& inc = state.active()->incrementalOptimizer;
inc.setRoot(ast);
std::string tidFold = inc.applyTransform("constant-fold");
std::string tidDce = inc.applyTransform("dead-code-elim");
auto history = inc.getTransformHistory();
size_t totalNodes = 0;
for (const auto& h : history) {
if (h.transformId == tidFold || h.transformId == tidDce) {
totalNodes += h.affectedNodeIds.size();
}
}
std::string warning = findOptimizationLockWarning(ast);
std::string summary = "Apply All: ";
if (totalNodes > 0) {
summary += "applied (" + std::to_string(totalNodes) + " nodes)";
} else {
summary += "no changes";
}
if (!warning.empty()) {
summary += " — warning: " + warning;
state.notify(NotificationLevel::Warning, warning);
}
state.optimizeApplySummary = summary;
state.notify(NotificationLevel::Info, summary);
if (totalNodes > 0) state.applyOrchestratorToActive();
std::string afterText = state.active()->editBuf;
state.openDiff(beforeText, afterText, false, 3, {tidFold, tidDce});
}
}
showBlockedTooltip(blockReason);
if (blocked) ImGui::EndDisabled();
if (!state.optimizeApplySummary.empty()) {
ImGui::TextWrapped("%s", state.optimizeApplySummary.c_str());
}
}
ImGui::PopFont();
ImGui::EndTabItem();
}
// Transform history
if (ImGui::BeginTabItem("Transforms")) {
ImGui::PushFont(state.uiFont);
auto* buf = state.active();
Module* ast = state.mutationAST();
if (!buf) {
ImGui::TextDisabled("(no active buffer)");
} else if (buf->bufferMode == BufferManager::BufferMode::Text) {
ImGui::TextDisabled("(disabled in Text mode)");
} else if (!ast) {
ImGui::TextDisabled("(no AST)");
} else {
auto& inc = buf->incrementalOptimizer;
inc.setRoot(ast);
auto history = inc.getTransformHistory();
const bool hasHistory = !history.empty();
if (!hasHistory) ImGui::BeginDisabled();
if (ImGui::Button("Undo All")) {
bool any = false;
while (inc.undoLast()) {
any = true;
}
if (any) state.applyOrchestratorToActive();
}
if (!hasHistory) ImGui::EndDisabled();
ImGui::Separator();
if (!hasHistory) {
ImGui::TextDisabled("(no transforms)");
} else {
for (const auto& h : history) {
ImGui::PushID(h.transformId.c_str());
ImVec4 color = transformColorForName(h.transformName);
ImGui::TextColored(color, "%s", h.transformName.c_str());
ImGui::SameLine();
ImGui::TextDisabled("@ %s", h.timestamp.c_str());
if (!h.actor.empty()) {
ImGui::SameLine();
ImGui::TextDisabled("by %s", h.actor.c_str());
}
ImGui::SameLine();
if (!h.reversible) ImGui::BeginDisabled();
if (ImGui::Button("Undo")) {
if (inc.undoTransform(h.transformId)) {
state.applyOrchestratorToActive();
}
}
if (!h.reversible) ImGui::EndDisabled();
ImGui::Text("Affected: %d", (int)h.affectedNodeIds.size());
std::string nodeList;
for (size_t i = 0; i < h.affectedNodeIds.size() && i < 5; ++i) {
if (!nodeList.empty()) nodeList += ", ";
nodeList += h.affectedNodeIds[i];
}
if (h.affectedNodeIds.size() > 5) nodeList += ", ...";
if (nodeList.empty()) nodeList = "(none)";
ImGui::TextWrapped("Nodes: %s", nodeList.c_str());
ImGui::Separator();
ImGui::PopID();
}
}
}
ImGui::PopFont();
ImGui::EndTabItem();
}
// Diff view
if (ImGui::BeginTabItem("Diff")) {
ImGui::PushFont(state.uiFont);
auto* buf = state.active();
Module* ast = state.mutationAST();
if (!buf) {
ImGui::TextDisabled("(no active buffer)");
} else if (buf->bufferMode == BufferManager::BufferMode::Text) {
ImGui::TextDisabled("(diff view disabled in Text mode)");
} else if (!state.diff.active) {
ImGui::TextDisabled("(no diff)");
} else {
if (state.diff.preview) {
if (ImGui::Button("Apply")) {
if (ast && !buf->readOnly) {
if (state.diff.batch) {
BatchMutationAPI batch;
batch.setRoot(ast);
auto res = batch.applySequence(state.diff.batchMutations);
if (!res.success) {
state.notify(NotificationLevel::Error, res.error);
} else {
state.notify(NotificationLevel::Success,
"Refactor applied (" +
std::to_string(res.appliedCount) + " mutations).");
state.applyOrchestratorToActive();
}
} else {
buf->incrementalOptimizer.setRoot(ast);
if (state.diff.action == 1) {
buf->incrementalOptimizer.applyTransform("constant-fold");
} else if (state.diff.action == 2) {
buf->incrementalOptimizer.applyTransform("dead-code-elim");
} else if (state.diff.action == 3) {
buf->incrementalOptimizer.applyTransform("constant-fold");
buf->incrementalOptimizer.applyTransform("dead-code-elim");
}
state.applyOrchestratorToActive();
}
}
state.diff.active = false;
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
state.diff.active = false;
}
} else {
if (ImGui::Button("Keep")) {
state.diff.active = false;
}
ImGui::SameLine();
if (ImGui::Button("Undo")) {
if (ast && !buf->readOnly) {
buf->incrementalOptimizer.setRoot(ast);
for (auto it = state.diff.transformIds.rbegin();
it != state.diff.transformIds.rend(); ++it) {
buf->incrementalOptimizer.undoTransform(*it);
}
state.applyOrchestratorToActive();
}
state.diff.active = false;
}
}
ImGui::Separator();
ImGui::BeginTable("##diffSplit", 2,
ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchProp);
ImGui::TableSetupColumn("Before", ImGuiTableColumnFlags_WidthStretch, 0.5f);
ImGui::TableSetupColumn("After", ImGuiTableColumnFlags_WidthStretch, 0.5f);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
CodeEditorOptions leftOpts;
leftOpts.readOnly = true;
leftOpts.showWhitespace = state.ui.showWhitespace;
leftOpts.showLineNumbers = state.ui.showLineNumbers;
leftOpts.showCurrentLine = false;
leftOpts.highlightLines = &state.diff.beforeLines;
leftOpts.highlightLineColor = IM_COL32(160, 80, 80, 120);
leftOpts.syncScrollX = &state.diffScrollX;
leftOpts.syncScrollY = &state.diffScrollY;
leftOpts.scrollMaster = true;
state.diffLeftWidget.render("##diffBefore",
state.diff.beforeText, std::vector<HighlightSpan>{}, leftOpts,
ImVec2(0, 0), state.monoFont);
ImGui::TableSetColumnIndex(1);
CodeEditorOptions rightOpts;
rightOpts.readOnly = true;
rightOpts.showWhitespace = state.ui.showWhitespace;
rightOpts.showLineNumbers = state.ui.showLineNumbers;
rightOpts.showCurrentLine = false;
rightOpts.highlightLines = &state.diff.afterLines;
rightOpts.highlightLineColor = IM_COL32(80, 160, 80, 120);
rightOpts.syncScrollX = &state.diffScrollX;
rightOpts.syncScrollY = &state.diffScrollY;
rightOpts.scrollMaster = false;
state.diffRightWidget.render("##diffAfter",
state.diff.afterText, std::vector<HighlightSpan>{}, rightOpts,
ImVec2(0, 0), state.monoFont);
ImGui::EndTable();
}
ImGui::PopFont();
ImGui::EndTabItem();
}
// AST view
if (ImGui::BeginTabItem("AST")) {
ImGui::PushFont(state.monoFont);
ImGui::BeginChild("##astScroll", ImVec2(0, 0), false);
Module* ast = state.active() ? state.active()->sync.getAST() : nullptr;
if (state.active() && state.active()->bufferMode == BufferManager::BufferMode::Text) {
ImGui::TextDisabled("(disabled in Text mode)");
} else if (ast) {
std::map<std::string, std::string> transformNames;
std::map<std::string, std::string> transformActors;
if (state.active()) {
auto history = state.active()->incrementalOptimizer.getTransformHistory();
for (const auto& h : history) {
transformNames[h.transformId] = h.transformName;
transformActors[h.transformId] = h.actor;
}
}
// Show basic AST info
ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f),
"Module: %s [%s]",
ast->name.c_str(), ast->targetLanguage.c_str());
ImGui::Separator();
auto functions = ast->getChildren("functions");
for (size_t i = 0; i < functions.size(); ++i) {
auto* fn = static_cast<Function*>(functions[i]);
std::string tid;
std::string tname;
if (state.active()) {
tid = state.active()->incrementalOptimizer.getProvenance(fn->id);
auto it = transformNames.find(tid);
if (it != transformNames.end()) tname = it->second;
}
ImVec4 fnColor = tname.empty() ?
ImVec4(0.86f, 0.86f, 0.55f, 1.0f) :
transformColorForName(tname);
ImGui::TextColored(fnColor, " Function: %s", fn->name.c_str());
if (!tname.empty() && ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
std::string actor = transformActors[tid];
if (actor.empty()) {
ImGui::Text("Transform: %s (%s)", tname.c_str(), tid.c_str());
} else {
ImGui::Text("Transform: %s (%s) by %s",
tname.c_str(), tid.c_str(), actor.c_str());
}
ImGui::EndTooltip();
}
auto params = fn->getChildren("parameters");
for (auto* p : params) {
auto* param = static_cast<Parameter*>(p);
std::string pid;
std::string pname;
if (state.active()) {
pid = state.active()->incrementalOptimizer.getProvenance(param->id);
auto it = transformNames.find(pid);
if (it != transformNames.end()) pname = it->second;
}
ImVec4 pColor = pname.empty() ?
ImVec4(0.6f, 0.78f, 0.9f, 1.0f) :
transformColorForName(pname);
ImGui::TextColored(pColor, " param: %s", param->name.c_str());
if (!pname.empty() && ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
std::string actor = transformActors[pid];
if (actor.empty()) {
ImGui::Text("Transform: %s (%s)", pname.c_str(), pid.c_str());
} else {
ImGui::Text("Transform: %s (%s) by %s",
pname.c_str(), pid.c_str(), actor.c_str());
}
ImGui::EndTooltip();
}
}
auto body = fn->getChildren("body");
ImGui::Text(" body: %d statement(s)", (int)body.size());
auto annos = fn->getChildren("annotations");
for (auto* a : annos) {
ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
" @%s", a->conceptType.c_str());
}
}
} else {
ImGui::TextDisabled("(no AST — enter some code)");
}
ImGui::EndChild();
ImGui::PopFont();
ImGui::EndTabItem();
}
// Syntax-highlighted preview
if (ImGui::BeginTabItem("Highlighted")) {
ImGui::PushFont(state.monoFont);
ImGui::BeginChild("##hlScroll", ImVec2(0, 0), false);
state.updateHighlights();
if (state.active())
RenderHighlightedText(state.active()->editBuf, state.active()->highlights);
ImGui::EndChild();
ImGui::PopFont();
ImGui::EndTabItem();
}
// Generated code preview
if (ImGui::BeginTabItem("Generated")) {
ImGui::PushFont(state.monoFont);
ImGui::BeginChild("##genScroll", ImVec2(0, 0), false);
Module* ast = state.active() ? state.active()->sync.getAST() : nullptr;
if (state.active() && state.active()->bufferMode == BufferManager::BufferMode::Text) {
ImGui::TextDisabled("(disabled in Text mode)");
} else {
state.updateGenerated();
if (ast && state.active()) {
ImGui::TextUnformatted(state.active()->generatedBuf.c_str());
} else {
ImGui::TextDisabled("(no AST)");
}
}
ImGui::EndChild();
ImGui::PopFont();
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::End();
}