Files
whetstone_DSL/editor/src/panels/StatusBarPanel.h
2026-02-17 21:32:46 -07:00

232 lines
8.3 KiB
C++

#pragma once
#include "../EditorState.h"
#include "../EditorUtils.h"
#include "../AgentTaskStatusOverlay.h"
#include "../ThemeEngine.h"
#include <algorithm>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <string>
#ifdef _WIN32
#include <psapi.h>
#include <windows.h>
#endif
static std::string detectGitBranch(const std::string& root) {
if (root.empty()) return "-";
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.empty()) return line.substr(0, 8);
return "-";
}
static void toggleBufferMode(EditorState& state) {
if (!state.active()) return;
BufferManager::BufferMode next =
state.active()->bufferMode == BufferManager::BufferMode::Text
? BufferManager::BufferMode::Structured
: BufferManager::BufferMode::Text;
state.setActiveBufferMode(next);
}
static std::string detectLineEnding(const std::string& text) {
if (text.find("\r\n") != std::string::npos) return "CRLF";
return "LF";
}
static size_t processMemoryBytes() {
#ifdef _WIN32
PROCESS_MEMORY_COUNTERS_EX pmc{};
if (GetProcessMemoryInfo(GetCurrentProcess(),
reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&pmc),
sizeof(pmc))) {
return static_cast<size_t>(pmc.WorkingSetSize);
}
#endif
return 0;
}
static std::string formatMemory(size_t bytes) {
if (bytes == 0) return "-";
const double mb = 1024.0 * 1024.0;
char buf[64];
std::snprintf(buf, sizeof(buf), "%.0f MB", bytes / mb);
return buf;
}
static ImVec4 statusBarBackgroundColor(const EditorState& state) {
bool hasError = false;
for (const auto& d : state.whetstoneDiagnostics) {
if (d.severity == 1) {
hasError = true;
break;
}
}
if (!hasError) {
for (const auto& d : state.emacsDiagnostics) {
if (d.severity == 1) {
hasError = true;
break;
}
}
}
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 (state.active() &&
state.active()->bufferMode == BufferManager::BufferMode::Text &&
state.active()->fileSizeBytes > 0) {
ImGui::SameLine(0, 6);
if (ImGui::Button("Reopen Structured")) {
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::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"};
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.");
}
ImGui::SameLine(0, 10);
std::string lineEnding = state.active() ? detectLineEnding(state.active()->editBuf) : "-";
if (ImGui::Button(lineEnding.c_str())) {
state.notify(NotificationLevel::Info, "Line ending toggle not yet implemented.");
}
}
static void renderStatusBarCenter(EditorState& state, float windowWidth) {
std::string centerText = "(no notifications)";
if (const Notification* n = state.notifications.latest()) centerText = n->message;
float centerWidth = ImGui::CalcTextSize(centerText.c_str()).x;
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;
}
}
static void renderRecordingAndAgentIndicator(EditorState& state) {
ImGui::SameLine(0, 10);
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::IsItemHovered()) {
ImGui::SetTooltip(recording ? "Session recording active" : "Start session recording");
}
if (recording) ImGui::PopStyleColor(2);
AgentTaskStatusSummary taskSummary =
AgentTaskStatusOverlay::buildSummary(state.agent.taskSlots);
if (!taskSummary.hasWork) return;
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;
int selLines = 0;
if (state.active() && state.active()->widget.hasSelectionRange()) {
state.active()->widget.getSelectionRange(selStart, selEnd);
if (selStart >= 0 && selEnd > selStart) {
selChars = selEnd - selStart;
selLines = 1;
for (int i = selStart; i < selEnd && i < (int)state.active()->editBuf.size(); ++i) {
if (state.active()->editBuf[i] == '\n') ++selLines;
}
}
}
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();
ImGui::PopStyleVar();
ImGui::PopStyleColor();
}