696 lines
33 KiB
C++
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();
|
|
}
|