Sprint 37: architecture refactor pass

This commit is contained in:
Bill
2026-02-17 21:32:46 -07:00
parent b5f03bd7ae
commit 98eae97f24
3 changed files with 327 additions and 230 deletions

View File

@@ -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();