Sprint 37: architecture refactor pass
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#include "../AgentTaskStatusOverlay.h"
|
||||
#include "../EditorState.h"
|
||||
#include "../EditorUtils.h"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
static int countChatContextNodes(const ASTNode* node) {
|
||||
@@ -15,71 +16,69 @@ static int countChatContextNodes(const ASTNode* node) {
|
||||
return total;
|
||||
}
|
||||
|
||||
static void renderAgentChatPanel(EditorState& state) {
|
||||
if (!state.ui.showAgentChatPanel) return;
|
||||
if (!ImGui::Begin("Agent Chat", &state.ui.showAgentChatPanel)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
static void syncChatSessionForProject(EditorState& state,
|
||||
AgentChatState& chat,
|
||||
const std::string& projectFile) {
|
||||
if (projectFile.empty()) return;
|
||||
if (chat.sessionLoaded && chat.loadedProjectFile == projectFile) return;
|
||||
|
||||
AgentChatState& chat = state.agent.chat;
|
||||
AgentTaskSlotsState& taskSlots = state.agent.taskSlots;
|
||||
const std::string projectFile = state.activeBuffer ? state.activeBuffer->path : "";
|
||||
if (!projectFile.empty() &&
|
||||
(!chat.sessionLoaded || chat.loadedProjectFile != projectFile)) {
|
||||
AgentChatSavedSession loaded;
|
||||
std::string error;
|
||||
if (AgentChatSessionPersistence::loadLatestSession(state.workspaceRoot,
|
||||
projectFile,
|
||||
&loaded,
|
||||
&error)) {
|
||||
chat = loaded.chat;
|
||||
chat.loadedProjectFile = projectFile;
|
||||
chat.sessionLoaded = true;
|
||||
chat.persistedMessageCount = chat.messages.size();
|
||||
} else {
|
||||
chat.loadedProjectFile = projectFile;
|
||||
chat.sessionLoaded = true;
|
||||
}
|
||||
AgentChatSavedSession loaded;
|
||||
std::string error;
|
||||
if (AgentChatSessionPersistence::loadLatestSession(state.workspaceRoot,
|
||||
projectFile,
|
||||
&loaded,
|
||||
&error)) {
|
||||
chat = loaded.chat;
|
||||
chat.persistedMessageCount = chat.messages.size();
|
||||
}
|
||||
chat.loadedProjectFile = projectFile;
|
||||
chat.sessionLoaded = true;
|
||||
}
|
||||
|
||||
if (chat.systemContext.empty()) {
|
||||
AgentChatRuntimeSnapshot snapshot;
|
||||
if (state.activeBuffer) {
|
||||
snapshot.activeFile = state.activeBuffer->path;
|
||||
snapshot.language = state.activeBuffer->language;
|
||||
}
|
||||
snapshot.workspacePath = state.workspaceRoot.empty() ? "." : state.workspaceRoot;
|
||||
Module* ast = state.activeAST();
|
||||
snapshot.astNodeCount = countChatContextNodes(ast);
|
||||
snapshot.openAnnotationCount = countAnnotationNodes(ast);
|
||||
AgentChatContextInjector::inject(&chat, snapshot, "context");
|
||||
static void ensureChatSystemContext(EditorState& state, AgentChatState& chat) {
|
||||
if (!chat.systemContext.empty()) return;
|
||||
|
||||
AgentChatRuntimeSnapshot snapshot;
|
||||
if (state.activeBuffer) {
|
||||
snapshot.activeFile = state.activeBuffer->path;
|
||||
snapshot.language = state.activeBuffer->language;
|
||||
}
|
||||
snapshot.workspacePath = state.workspaceRoot.empty() ? "." : state.workspaceRoot;
|
||||
|
||||
Module* ast = state.activeAST();
|
||||
snapshot.astNodeCount = countChatContextNodes(ast);
|
||||
snapshot.openAnnotationCount = countAnnotationNodes(ast);
|
||||
AgentChatContextInjector::inject(&chat, snapshot, "context");
|
||||
}
|
||||
|
||||
static std::vector<AgentTaskOverlayRow> buildTaskOverlayRows(AgentTaskSlotsState& taskSlots) {
|
||||
(void)AgentTaskSlots::assignPendingToSlots(&taskSlots);
|
||||
const int nowSeconds = static_cast<int>(std::time(nullptr));
|
||||
const auto overlayRows = AgentTaskStatusOverlay::buildRows(taskSlots, nowSeconds);
|
||||
return AgentTaskStatusOverlay::buildRows(taskSlots, nowSeconds);
|
||||
}
|
||||
|
||||
if (!overlayRows.empty() && ImGui::BeginTabBar("##AgentTaskSlotsTabs")) {
|
||||
for (const auto& row : overlayRows) {
|
||||
std::string tabName = (row.slotIndex >= 0)
|
||||
? ("Slot " + std::to_string(row.slotIndex + 1) + ": " + row.taskId)
|
||||
: ("Pending: " + row.taskId);
|
||||
if (ImGui::BeginTabItem(tabName.c_str())) {
|
||||
ImGui::Text("Task: %s", row.description.c_str());
|
||||
ImGui::Text("Status: %s", row.status.c_str());
|
||||
ImGui::Text("Elapsed: %ds", row.elapsedSeconds);
|
||||
ImGui::Text("Tool Calls: %d", row.toolCallsMade);
|
||||
ImGui::Text("Current Step: %s", row.currentStep.c_str());
|
||||
if (row.canCancel &&
|
||||
ImGui::Button(("Cancel##tab_" + row.taskId).c_str())) {
|
||||
(void)AgentTaskStatusOverlay::cancelTask(&taskSlots, row.taskId);
|
||||
}
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
static void renderTaskSlotTabs(AgentTaskSlotsState& taskSlots,
|
||||
const std::vector<AgentTaskOverlayRow>& rows) {
|
||||
if (rows.empty() || !ImGui::BeginTabBar("##AgentTaskSlotsTabs")) return;
|
||||
for (const auto& row : rows) {
|
||||
std::string tabName = (row.slotIndex >= 0)
|
||||
? ("Slot " + std::to_string(row.slotIndex + 1) + ": " + row.taskId)
|
||||
: ("Pending: " + row.taskId);
|
||||
if (!ImGui::BeginTabItem(tabName.c_str())) continue;
|
||||
ImGui::Text("Task: %s", row.description.c_str());
|
||||
ImGui::Text("Status: %s", row.status.c_str());
|
||||
ImGui::Text("Elapsed: %ds", row.elapsedSeconds);
|
||||
ImGui::Text("Tool Calls: %d", row.toolCallsMade);
|
||||
ImGui::Text("Current Step: %s", row.currentStep.c_str());
|
||||
if (row.canCancel && ImGui::Button(("Cancel##tab_" + row.taskId).c_str())) {
|
||||
(void)AgentTaskStatusOverlay::cancelTask(&taskSlots, row.taskId);
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
static void renderChatMessageHistory(AgentChatState& chat) {
|
||||
ImGui::BeginChild("##AgentChatHistory", ImVec2(0, -120), true);
|
||||
for (const auto& msg : chat.messages) {
|
||||
ImGui::Text("[%s] %s", msg.timestamp.c_str(), AgentChatPanelModel::roleLabel(msg.role));
|
||||
@@ -92,113 +91,152 @@ static void renderAgentChatPanel(EditorState& state) {
|
||||
}
|
||||
chat.autoScroll = false;
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
if (!chat.toolCalls.empty()) {
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted("Tool Calls");
|
||||
for (std::size_t i = 0; i < chat.toolCalls.size(); ++i) {
|
||||
auto& call = chat.toolCalls[i];
|
||||
std::string label = AgentToolCallVisualization::inlineLabel(call);
|
||||
if (ImGui::CollapsingHeader((label + "##tool_" + std::to_string(i)).c_str())) {
|
||||
call.expanded = true;
|
||||
ImGui::TextUnformatted("Input");
|
||||
ImGui::BeginChild(("##tool_in_" + std::to_string(i)).c_str(), ImVec2(-1, 70), true);
|
||||
ImGui::TextUnformatted(call.inputJson.c_str());
|
||||
ImGui::EndChild();
|
||||
ImGui::TextUnformatted("Output");
|
||||
ImGui::BeginChild(("##tool_out_" + std::to_string(i)).c_str(), ImVec2(-1, 70), true);
|
||||
ImGui::TextUnformatted(call.outputJson.c_str());
|
||||
ImGui::EndChild();
|
||||
} else {
|
||||
call.expanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!chat.mutationPreviews.empty()) {
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted("Mutation Previews");
|
||||
for (std::size_t i = 0; i < chat.mutationPreviews.size(); ++i) {
|
||||
const auto& preview = chat.mutationPreviews[i];
|
||||
AgentMutationApproval::ensureRecord(&chat.mutationApprovals, preview.previewId);
|
||||
const MutationApprovalRecord* approval =
|
||||
AgentMutationApproval::find(chat.mutationApprovals, preview.previewId);
|
||||
std::string header = preview.previewId + (preview.hasChanges ? " (changes)" : " (no changes)");
|
||||
if (ImGui::CollapsingHeader((header + "##preview_" + std::to_string(i)).c_str())) {
|
||||
ImGui::TextUnformatted("Mutation JSON");
|
||||
ImGui::BeginChild(("##preview_json_" + std::to_string(i)).c_str(), ImVec2(-1, 60), true);
|
||||
ImGui::TextUnformatted(preview.mutationJson.c_str());
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::Columns(2, ("##preview_cols_" + std::to_string(i)).c_str(), true);
|
||||
ImGui::TextUnformatted("Before");
|
||||
ImGui::BeginChild(("##preview_before_" + std::to_string(i)).c_str(), ImVec2(0, 100), true);
|
||||
ImGui::TextUnformatted(preview.beforeCode.c_str());
|
||||
ImGui::EndChild();
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextUnformatted("After");
|
||||
ImGui::BeginChild(("##preview_after_" + std::to_string(i)).c_str(), ImVec2(0, 100), true);
|
||||
ImGui::TextUnformatted(preview.afterCode.c_str());
|
||||
ImGui::EndChild();
|
||||
ImGui::Columns(1);
|
||||
|
||||
ImGui::Separator();
|
||||
if (approval) {
|
||||
ImGui::Text("Decision: %s", AgentMutationApproval::decisionText(approval->decision).c_str());
|
||||
}
|
||||
if (ImGui::Button(("Accept##" + preview.previewId).c_str())) {
|
||||
AgentMutationApproval::accept(&chat.mutationApprovals, preview.previewId);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(("Reject##" + preview.previewId).c_str())) {
|
||||
AgentMutationApproval::reject(&chat.mutationApprovals, preview.previewId, "user_rejected");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(("Modify##" + preview.previewId).c_str())) {
|
||||
AgentMutationApproval::modify(&chat.mutationApprovals,
|
||||
preview.previewId,
|
||||
preview.mutationJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!overlayRows.empty()) {
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted("Agent Tasks");
|
||||
for (const auto& row : overlayRows) {
|
||||
ImGui::Text("[%s] %s", row.status.c_str(), row.description.c_str());
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(elapsed: %ds, tools: %d, step: %s)",
|
||||
row.elapsedSeconds,
|
||||
row.toolCallsMade,
|
||||
row.currentStep.c_str());
|
||||
if (row.canCancel &&
|
||||
ImGui::Button(("Cancel##row_" + row.taskId).c_str())) {
|
||||
(void)AgentTaskStatusOverlay::cancelTask(&taskSlots, row.taskId);
|
||||
}
|
||||
static void renderToolCallSection(AgentChatState& chat) {
|
||||
if (chat.toolCalls.empty()) return;
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted("Tool Calls");
|
||||
for (std::size_t i = 0; i < chat.toolCalls.size(); ++i) {
|
||||
auto& call = chat.toolCalls[i];
|
||||
std::string label = AgentToolCallVisualization::inlineLabel(call);
|
||||
if (!ImGui::CollapsingHeader((label + "##tool_" + std::to_string(i)).c_str())) {
|
||||
call.expanded = false;
|
||||
continue;
|
||||
}
|
||||
call.expanded = true;
|
||||
ImGui::TextUnformatted("Input");
|
||||
ImGui::BeginChild(("##tool_in_" + std::to_string(i)).c_str(), ImVec2(-1, 70), true);
|
||||
ImGui::TextUnformatted(call.inputJson.c_str());
|
||||
ImGui::EndChild();
|
||||
ImGui::TextUnformatted("Output");
|
||||
ImGui::BeginChild(("##tool_out_" + std::to_string(i)).c_str(), ImVec2(-1, 70), true);
|
||||
ImGui::TextUnformatted(call.outputJson.c_str());
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
static void renderSingleMutationPreview(AgentChatState& chat,
|
||||
const AgentMutationPreview& preview,
|
||||
std::size_t index) {
|
||||
AgentMutationApproval::ensureRecord(&chat.mutationApprovals, preview.previewId);
|
||||
const MutationApprovalRecord* approval =
|
||||
AgentMutationApproval::find(chat.mutationApprovals, preview.previewId);
|
||||
|
||||
std::string header = preview.previewId + (preview.hasChanges ? " (changes)" : " (no changes)");
|
||||
if (!ImGui::CollapsingHeader((header + "##preview_" + std::to_string(index)).c_str())) return;
|
||||
|
||||
ImGui::TextUnformatted("Mutation JSON");
|
||||
ImGui::BeginChild(("##preview_json_" + std::to_string(index)).c_str(), ImVec2(-1, 60), true);
|
||||
ImGui::TextUnformatted(preview.mutationJson.c_str());
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::Columns(2, ("##preview_cols_" + std::to_string(index)).c_str(), true);
|
||||
ImGui::TextUnformatted("Before");
|
||||
ImGui::BeginChild(("##preview_before_" + std::to_string(index)).c_str(), ImVec2(0, 100), true);
|
||||
ImGui::TextUnformatted(preview.beforeCode.c_str());
|
||||
ImGui::EndChild();
|
||||
ImGui::NextColumn();
|
||||
ImGui::TextUnformatted("After");
|
||||
ImGui::BeginChild(("##preview_after_" + std::to_string(index)).c_str(), ImVec2(0, 100), true);
|
||||
ImGui::TextUnformatted(preview.afterCode.c_str());
|
||||
ImGui::EndChild();
|
||||
ImGui::Columns(1);
|
||||
|
||||
ImGui::Separator();
|
||||
if (approval) {
|
||||
ImGui::Text("Decision: %s", AgentMutationApproval::decisionText(approval->decision).c_str());
|
||||
}
|
||||
if (ImGui::Button(("Accept##" + preview.previewId).c_str())) {
|
||||
AgentMutationApproval::accept(&chat.mutationApprovals, preview.previewId);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(("Reject##" + preview.previewId).c_str())) {
|
||||
AgentMutationApproval::reject(&chat.mutationApprovals, preview.previewId, "user_rejected");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(("Modify##" + preview.previewId).c_str())) {
|
||||
AgentMutationApproval::modify(&chat.mutationApprovals, preview.previewId, preview.mutationJson);
|
||||
}
|
||||
}
|
||||
|
||||
static void renderMutationPreviewSection(AgentChatState& chat) {
|
||||
if (chat.mutationPreviews.empty()) return;
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted("Mutation Previews");
|
||||
for (std::size_t i = 0; i < chat.mutationPreviews.size(); ++i) {
|
||||
renderSingleMutationPreview(chat, chat.mutationPreviews[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
static void renderAgentTaskRows(AgentTaskSlotsState& taskSlots,
|
||||
const std::vector<AgentTaskOverlayRow>& rows) {
|
||||
if (rows.empty()) return;
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted("Agent Tasks");
|
||||
for (const auto& row : rows) {
|
||||
ImGui::Text("[%s] %s", row.status.c_str(), row.description.c_str());
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(elapsed: %ds, tools: %d, step: %s)",
|
||||
row.elapsedSeconds,
|
||||
row.toolCallsMade,
|
||||
row.currentStep.c_str());
|
||||
if (row.canCancel && ImGui::Button(("Cancel##row_" + row.taskId).c_str())) {
|
||||
(void)AgentTaskStatusOverlay::cancelTask(&taskSlots, row.taskId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void renderChatComposer(AgentChatState& chat) {
|
||||
ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput;
|
||||
InputTextMultilineStr("##AgentChatInput", &chat.draftInput, ImVec2(-90, 80), flags);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Send", ImVec2(80, 80))) {
|
||||
AgentChatPanelModel::sendUserMessage(&chat, "now");
|
||||
}
|
||||
}
|
||||
|
||||
if (!projectFile.empty() &&
|
||||
chat.sessionLoaded &&
|
||||
chat.messages.size() != chat.persistedMessageCount) {
|
||||
std::string error;
|
||||
if (AgentChatSessionPersistence::saveSession(state.workspaceRoot,
|
||||
projectFile,
|
||||
"autosave",
|
||||
chat,
|
||||
&error)) {
|
||||
chat.persistedMessageCount = chat.messages.size();
|
||||
}
|
||||
static void autosaveChatSession(EditorState& state,
|
||||
const std::string& projectFile,
|
||||
AgentChatState& chat) {
|
||||
if (projectFile.empty() || !chat.sessionLoaded) return;
|
||||
if (chat.messages.size() == chat.persistedMessageCount) return;
|
||||
|
||||
std::string error;
|
||||
if (AgentChatSessionPersistence::saveSession(state.workspaceRoot,
|
||||
projectFile,
|
||||
"autosave",
|
||||
chat,
|
||||
&error)) {
|
||||
chat.persistedMessageCount = chat.messages.size();
|
||||
}
|
||||
}
|
||||
|
||||
static void renderAgentChatPanel(EditorState& state) {
|
||||
if (!state.ui.showAgentChatPanel) return;
|
||||
if (!ImGui::Begin("Agent Chat", &state.ui.showAgentChatPanel)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
AgentChatState& chat = state.agent.chat;
|
||||
AgentTaskSlotsState& taskSlots = state.agent.taskSlots;
|
||||
const std::string projectFile = state.activeBuffer ? state.activeBuffer->path : "";
|
||||
|
||||
syncChatSessionForProject(state, chat, projectFile);
|
||||
ensureChatSystemContext(state, chat);
|
||||
|
||||
const auto overlayRows = buildTaskOverlayRows(taskSlots);
|
||||
renderTaskSlotTabs(taskSlots, overlayRows);
|
||||
renderChatMessageHistory(chat);
|
||||
renderToolCallSection(chat);
|
||||
renderMutationPreviewSection(chat);
|
||||
renderAgentTaskRows(taskSlots, overlayRows);
|
||||
renderChatComposer(chat);
|
||||
autosaveChatSession(state, projectFile, chat);
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "../EditorState.h"
|
||||
#include "../EditorUtils.h"
|
||||
#include "../AgentTaskStatusOverlay.h"
|
||||
#include "../ThemeEngine.h"
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
static std::string detectGitBranch(const std::string& root) {
|
||||
@@ -17,12 +21,11 @@ static std::string detectGitBranch(const std::string& root) {
|
||||
std::filesystem::path headPath = std::filesystem::path(root) / ".git" / "HEAD";
|
||||
std::ifstream in(headPath.string());
|
||||
if (!in.is_open()) return "-";
|
||||
|
||||
std::string line;
|
||||
std::getline(in, line);
|
||||
const std::string refPrefix = "ref: refs/heads/";
|
||||
if (line.rfind(refPrefix, 0) == 0) {
|
||||
return line.substr(refPrefix.size());
|
||||
}
|
||||
if (line.rfind(refPrefix, 0) == 0) return line.substr(refPrefix.size());
|
||||
if (!line.empty()) return line.substr(0, 8);
|
||||
return "-";
|
||||
}
|
||||
@@ -56,49 +59,39 @@ static size_t processMemoryBytes() {
|
||||
static std::string formatMemory(size_t bytes) {
|
||||
if (bytes == 0) return "-";
|
||||
const double mb = 1024.0 * 1024.0;
|
||||
double value = bytes / mb;
|
||||
char buf[64];
|
||||
std::snprintf(buf, sizeof(buf), "%.0f MB", value);
|
||||
std::snprintf(buf, sizeof(buf), "%.0f MB", bytes / mb);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void renderStatusBar(EditorState& state) {
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImGuiWindowFlags sbFlags = ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoSavedSettings;
|
||||
float sbHeight = ImGui::GetFrameHeight() + 2;
|
||||
ImVec2 sbPos(viewport->WorkPos.x, viewport->WorkPos.y + viewport->WorkSize.y - sbHeight);
|
||||
ImVec2 sbSize(viewport->WorkSize.x, sbHeight);
|
||||
ImGui::SetNextWindowPos(sbPos);
|
||||
ImGui::SetNextWindowSize(sbSize);
|
||||
ImVec4 bg = ImVec4(0.10f, 0.12f, 0.16f, 1.0f);
|
||||
static ImVec4 statusBarBackgroundColor(const EditorState& state) {
|
||||
bool hasError = false;
|
||||
for (const auto& d : state.whetstoneDiagnostics) {
|
||||
if (d.severity == 1) { hasError = true; break; }
|
||||
if (d.severity == 1) {
|
||||
hasError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasError) {
|
||||
for (const auto& d : state.emacsDiagnostics) {
|
||||
if (d.severity == 1) { hasError = true; break; }
|
||||
if (d.severity == 1) {
|
||||
hasError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (state.build.runInProgress) {
|
||||
bg = ImVec4(0.10f, 0.35f, 0.25f, 1.0f);
|
||||
} else if (hasError) {
|
||||
bg = ImVec4(0.45f, 0.12f, 0.12f, 1.0f);
|
||||
}
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, bg);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 2));
|
||||
ImGui::Begin("##StatusBar", nullptr, sbFlags);
|
||||
ImGui::PushFont(state.uiFont);
|
||||
|
||||
// Left cluster: mode, language, encoding, line ending
|
||||
if (state.build.runInProgress) return ImVec4(0.10f, 0.35f, 0.25f, 1.0f);
|
||||
if (hasError) return ImVec4(0.45f, 0.12f, 0.12f, 1.0f);
|
||||
return ImVec4(0.10f, 0.12f, 0.16f, 1.0f);
|
||||
}
|
||||
|
||||
static void renderStatusBarLeftCluster(EditorState& state) {
|
||||
std::string modeLabel = state.active()
|
||||
? (state.active()->bufferMode == BufferManager::BufferMode::Text ? "Text Mode" : "Structured Mode")
|
||||
: "-";
|
||||
if (ImGui::Button(modeLabel.c_str())) {
|
||||
toggleBufferMode(state);
|
||||
}
|
||||
if (ImGui::Button(modeLabel.c_str())) toggleBufferMode(state);
|
||||
|
||||
if (state.active() &&
|
||||
state.active()->bufferMode == BufferManager::BufferMode::Text &&
|
||||
state.active()->fileSizeBytes > 0) {
|
||||
@@ -107,19 +100,19 @@ static void renderStatusBar(EditorState& state) {
|
||||
state.setActiveBufferMode(BufferManager::BufferMode::Structured);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine(0, 10);
|
||||
const char* langLabel = state.active() ? state.active()->language.c_str() : "-";
|
||||
if (ImGui::Button(langLabel)) {
|
||||
ImGui::OpenPopup("##langPopup");
|
||||
}
|
||||
if (ImGui::Button(langLabel)) ImGui::OpenPopup("##langPopup");
|
||||
if (ImGui::BeginPopup("##langPopup")) {
|
||||
const char* langs[] = {"python","cpp","elisp","javascript","typescript","java","rust","go","org"};
|
||||
const char* labels[] = {"Python","C++","Elisp","JavaScript","TypeScript","Java","Rust","Go","Org"};
|
||||
const char* langs[] = {"python", "cpp", "elisp", "javascript", "typescript", "java", "rust", "go", "org"};
|
||||
const char* labels[] = {"Python", "C++", "Elisp", "JavaScript", "TypeScript", "Java", "Rust", "Go", "Org"};
|
||||
for (int i = 0; i < IM_ARRAYSIZE(langs); ++i) {
|
||||
if (ImGui::MenuItem(labels[i])) state.setLanguage(langs[i]);
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::SameLine(0, 10);
|
||||
if (ImGui::Button("UTF-8")) {
|
||||
state.notify(NotificationLevel::Info, "Encoding settings not yet configurable.");
|
||||
@@ -129,48 +122,45 @@ static void renderStatusBar(EditorState& state) {
|
||||
if (ImGui::Button(lineEnding.c_str())) {
|
||||
state.notify(NotificationLevel::Info, "Line ending toggle not yet implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
// Center: notifications
|
||||
static void renderStatusBarCenter(EditorState& state, float windowWidth) {
|
||||
std::string centerText = "(no notifications)";
|
||||
if (const Notification* n = state.notifications.latest()) {
|
||||
centerText = n->message;
|
||||
}
|
||||
if (const Notification* n = state.notifications.latest()) centerText = n->message;
|
||||
|
||||
float centerWidth = ImGui::CalcTextSize(centerText.c_str()).x;
|
||||
float windowWidth = ImGui::GetWindowWidth();
|
||||
float centerX = std::max(0.0f, (windowWidth * 0.5f) - (centerWidth * 0.5f));
|
||||
ImGui::SetCursorPosX(centerX);
|
||||
ImGui::TextUnformatted(centerText.c_str());
|
||||
if (ImGui::IsItemClicked()) {
|
||||
state.notifications.showHistory = !state.notifications.showHistory;
|
||||
}
|
||||
}
|
||||
|
||||
// Recording indicator
|
||||
static void renderRecordingAndAgentIndicator(EditorState& state) {
|
||||
ImGui::SameLine(0, 10);
|
||||
bool recording = state.agent.workflowRecorder.isRecording();
|
||||
const bool recording = state.agent.workflowRecorder.isRecording();
|
||||
if (recording) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.75f, 0.15f, 0.15f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.85f, 0.20f, 0.20f, 1.0f));
|
||||
}
|
||||
if (ImGui::Button("REC")) {
|
||||
state.toggleSessionRecording();
|
||||
}
|
||||
if (ImGui::Button("REC")) state.toggleSessionRecording();
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(recording ? "Session recording active" : "Start session recording");
|
||||
}
|
||||
if (recording) {
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
if (recording) ImGui::PopStyleColor(2);
|
||||
|
||||
AgentTaskStatusSummary taskSummary =
|
||||
AgentTaskStatusOverlay::buildSummary(state.agent.taskSlots);
|
||||
if (taskSummary.hasWork) {
|
||||
ImGui::SameLine(0, 10);
|
||||
if (ImGui::Button(taskSummary.label.c_str())) {
|
||||
state.ui.showAgentChatPanel = true;
|
||||
}
|
||||
}
|
||||
if (!taskSummary.hasWork) return;
|
||||
|
||||
// Right cluster: Ln/Col, selection, zoom, git
|
||||
ImGui::SameLine(0, 10);
|
||||
if (ImGui::Button(taskSummary.label.c_str())) {
|
||||
state.ui.showAgentChatPanel = true;
|
||||
}
|
||||
}
|
||||
|
||||
static std::string buildStatusBarRightText(EditorState& state) {
|
||||
int selStart = -1;
|
||||
int selEnd = -1;
|
||||
int selChars = 0;
|
||||
@@ -185,31 +175,54 @@ static void renderStatusBar(EditorState& state) {
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string rightText;
|
||||
if (state.active()) {
|
||||
rightText = "Ln " + std::to_string(state.active()->cursorLine) +
|
||||
", Col " + std::to_string(state.active()->cursorCol);
|
||||
} else {
|
||||
rightText = "Ln -, Col -";
|
||||
}
|
||||
if (selChars > 0) {
|
||||
rightText += " | Sel " + std::to_string(selChars) + "c " +
|
||||
std::to_string(selLines) + "l";
|
||||
}
|
||||
if (state.active() && state.active()->largeFileMode) {
|
||||
rightText += " | Large File";
|
||||
}
|
||||
size_t memBytes = processMemoryBytes();
|
||||
if (memBytes > 0) {
|
||||
rightText += " | Mem " + formatMemory(memBytes);
|
||||
}
|
||||
rightText += " | Zoom " + std::to_string(zoomPercent(state.settings.getFontSize(),
|
||||
state.baseFontSize)) + "%";
|
||||
rightText += " | Git " + detectGitBranch(state.workspaceRoot);
|
||||
|
||||
std::string rightText = state.active()
|
||||
? ("Ln " + std::to_string(state.active()->cursorLine) +
|
||||
", Col " + std::to_string(state.active()->cursorCol))
|
||||
: "Ln -, Col -";
|
||||
if (selChars > 0) {
|
||||
rightText += " | Sel " + std::to_string(selChars) + "c " + std::to_string(selLines) + "l";
|
||||
}
|
||||
if (state.active() && state.active()->largeFileMode) rightText += " | Large File";
|
||||
|
||||
size_t memBytes = processMemoryBytes();
|
||||
if (memBytes > 0) rightText += " | Mem " + formatMemory(memBytes);
|
||||
|
||||
rightText += " | Zoom " + std::to_string(zoomPercent(state.settings.getFontSize(),
|
||||
state.baseFontSize)) + "%";
|
||||
rightText += " | Git " + detectGitBranch(state.workspaceRoot);
|
||||
return rightText;
|
||||
}
|
||||
|
||||
static void renderStatusBarRight(EditorState& state, float windowWidth) {
|
||||
std::string rightText = buildStatusBarRightText(state);
|
||||
float rightWidth = ImGui::CalcTextSize(rightText.c_str()).x;
|
||||
ImGui::SetCursorPosX(std::max(0.0f, windowWidth - rightWidth - 8.0f));
|
||||
ImGui::TextUnformatted(rightText.c_str());
|
||||
}
|
||||
|
||||
static void renderStatusBar(EditorState& state) {
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImGuiWindowFlags sbFlags = ImGuiWindowFlags_NoDecoration |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoScrollbar |
|
||||
ImGuiWindowFlags_NoSavedSettings;
|
||||
const float sbHeight = ImGui::GetFrameHeight() + 2;
|
||||
const ImVec2 sbPos(viewport->WorkPos.x, viewport->WorkPos.y + viewport->WorkSize.y - sbHeight);
|
||||
const ImVec2 sbSize(viewport->WorkSize.x, sbHeight);
|
||||
|
||||
ImGui::SetNextWindowPos(sbPos);
|
||||
ImGui::SetNextWindowSize(sbSize);
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, statusBarBackgroundColor(state));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8, 2));
|
||||
ImGui::Begin("##StatusBar", nullptr, sbFlags);
|
||||
ImGui::PushFont(state.uiFont);
|
||||
|
||||
const float windowWidth = ImGui::GetWindowWidth();
|
||||
renderStatusBarLeftCluster(state);
|
||||
renderStatusBarCenter(state, windowWidth);
|
||||
renderRecordingAndAgentIndicator(state);
|
||||
renderStatusBarRight(state, windowWidth);
|
||||
|
||||
ImGui::PopFont();
|
||||
ImGui::End();
|
||||
|
||||
46
progress.md
46
progress.md
@@ -12779,3 +12779,49 @@ mutation preview acceptance, code update, and chat-session save/restore.
|
||||
- `editor/src/Sprint37IntegrationSummary.h` (`104` <= `600`)
|
||||
- `editor/tests/step633_test.cpp` within test-file size guidance (`109` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
## Sprint 37 Refactor Pass (Architecture Compliance)
|
||||
**Status:** PASS
|
||||
|
||||
Completed end-of-sprint modified-files audit for Steps 624-633 and refactored
|
||||
the two Sprint 37-touched UI functions that exceeded the `ARCHITECTURE.md`
|
||||
single-function line limit.
|
||||
|
||||
**Files modified:**
|
||||
- `editor/src/panels/AgentChatPanel.h`
|
||||
- `editor/src/panels/StatusBarPanel.h`
|
||||
|
||||
**Refactor result:**
|
||||
- Split `renderAgentChatPanel(...)` into focused helpers:
|
||||
- session sync, context injection, task-tab rendering, history rendering,
|
||||
tool-call rendering, mutation-preview rendering, task-row rendering,
|
||||
composer, autosave.
|
||||
- Split `renderStatusBar(...)` into focused helpers:
|
||||
- background-color selection, left cluster, center notification, recording +
|
||||
agent indicator, right-text composition/render.
|
||||
- Preserved panel behavior while bringing Sprint 37 touched code in line with
|
||||
function-size constraints.
|
||||
|
||||
**Verification run (full sprint matrix):**
|
||||
- `cmake --build editor/build-native --target whetstone_editor` - PASS
|
||||
- `cmake --build editor/build-native --target step624_test step625_test step626_test step627_test step628_test step629_test step630_test step631_test step632_test step633_test` - PASS
|
||||
- `./editor/build-native/step624_test` - PASS (12/12)
|
||||
- `./editor/build-native/step625_test` - PASS (12/12)
|
||||
- `./editor/build-native/step626_test` - PASS (12/12)
|
||||
- `./editor/build-native/step627_test` - PASS (12/12)
|
||||
- `./editor/build-native/step628_test` - PASS (8/8)
|
||||
- `./editor/build-native/step629_test` - PASS (12/12)
|
||||
- `./editor/build-native/step630_test` - PASS (12/12)
|
||||
- `./editor/build-native/step631_test` - PASS (12/12)
|
||||
- `./editor/build-native/step632_test` - PASS (12/12)
|
||||
- `./editor/build-native/step633_test` - PASS (8/8)
|
||||
- Sprint 37 matrix total: **112/112 passing**
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/panels/AgentChatPanel.h` (`242` <= `600`)
|
||||
- `editor/src/panels/StatusBarPanel.h` (`231` <= `600`)
|
||||
- `editor/src/Sprint37IntegrationSummary.h` (`104` <= `600`)
|
||||
- `editor/src/AgentTaskSlots.h` (`154` <= `600`)
|
||||
- `editor/src/AgentTaskStatusOverlay.h` (`79` <= `600`)
|
||||
- `editor/tests/step633_test.cpp` within test-file size guidance (`109` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
Reference in New Issue
Block a user