From de795bf6e68dacf6b55c11af439e4dc311f1c81e Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 16 Feb 2026 09:13:18 -0700 Subject: [PATCH] Step 357: Status Bar (12/12 tests) --- editor/CMakeLists.txt | 4 + editor/src/panels/StatusBar.h | 90 ++++++++++++++++++++ editor/tests/step357_test.cpp | 154 ++++++++++++++++++++++++++++++++++ progress.md | 36 ++++++++ 4 files changed, 284 insertions(+) create mode 100644 editor/src/panels/StatusBar.h create mode 100644 editor/tests/step357_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 5b6ac5b..eaee187 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -2122,4 +2122,8 @@ add_executable(step356_test tests/step356_test.cpp) target_include_directories(step356_test PRIVATE src) target_link_libraries(step356_test PRIVATE nlohmann_json::nlohmann_json) +add_executable(step357_test tests/step357_test.cpp) +target_include_directories(step357_test PRIVATE src) +target_link_libraries(step357_test PRIVATE nlohmann_json::nlohmann_json) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/panels/StatusBar.h b/editor/src/panels/StatusBar.h new file mode 100644 index 0000000..6b89a77 --- /dev/null +++ b/editor/src/panels/StatusBar.h @@ -0,0 +1,90 @@ +#pragma once +// Step 357: Status bar model. +// Headless representation used for UI rendering/tests. + +#include +#include "../ThemeData.h" + +struct StatusBarInput { + std::string language = "python"; + std::string filePath; + int line = 1; + int column = 1; + std::string encoding = "UTF-8"; + std::string lineEnding = "LF"; + bool workflowActive = false; + int workflowCurrent = 0; + int workflowTotal = 0; + bool mcpConnected = false; + int errorCount = 0; + int warningCount = 0; + int infoCount = 0; + int hintCount = 0; +}; + +struct StatusBarModel { + std::string languageBadge; + std::string leftText; + std::string centerText; + std::string rightText; + std::string workflowBadge; + std::string diagnosticsText; + std::string mcpText; + bool overflow = false; + + Color4 bgColor; + Color4 textColor; + Color4 dimTextColor; + Color4 languageBadgeColor; + Color4 diagnosticsColor; + Color4 workflowColor; +}; + +inline Color4 statusLanguageColor(const std::string& language, const WhetstoneTheme& theme) { + if (language == "python") return theme.syntaxFunction; + if (language == "cpp") return theme.syntaxType; + if (language == "kotlin") return theme.syntaxAnnotation; + if (language == "rust") return theme.accent; + if (language == "go") return theme.syntaxKeyword; + return theme.textAccent; +} + +inline StatusBarModel buildStatusBar(const StatusBarInput& in, + const WhetstoneTheme& theme, + size_t maxChars = 140) { + StatusBarModel m; + m.bgColor = theme.bgPanel; + m.textColor = theme.text; + m.dimTextColor = theme.textDim; + m.languageBadgeColor = statusLanguageColor(in.language, theme); + m.workflowColor = theme.textAccent; + m.diagnosticsColor = (in.errorCount > 0) ? theme.diagError : + (in.warningCount > 0 ? theme.diagWarning : theme.diagInfo); + + m.languageBadge = in.language; + m.leftText = in.language + " " + in.filePath; + m.centerText = "Ln " + std::to_string(in.line) + ", Col " + std::to_string(in.column) + + " | " + in.encoding + " | " + in.lineEnding; + m.diagnosticsText = std::to_string(in.errorCount) + " errors, " + + std::to_string(in.warningCount) + " warnings"; + m.mcpText = in.mcpConnected ? "MCP: connected" : "MCP: offline"; + + if (in.workflowActive && in.workflowTotal > 0) { + m.workflowBadge = "Executing " + std::to_string(in.workflowCurrent) + + "/" + std::to_string(in.workflowTotal); + } else { + m.workflowBadge = ""; + } + + m.rightText = m.diagnosticsText; + if (!m.workflowBadge.empty()) m.rightText += " | " + m.workflowBadge; + m.rightText += " | " + m.mcpText; + + const size_t total = m.leftText.size() + m.centerText.size() + m.rightText.size(); + m.overflow = total > maxChars; + return m; +} + +inline std::string statusBarClickDiagnosticsTarget() { + return "diagnostics"; +} diff --git a/editor/tests/step357_test.cpp b/editor/tests/step357_test.cpp new file mode 100644 index 0000000..6417932 --- /dev/null +++ b/editor/tests/step357_test.cpp @@ -0,0 +1,154 @@ +// Step 357: Status Bar (12 tests) + +#include +#include +#include +#include "panels/StatusBar.h" + +int main() { + int passed = 0; + WhetstoneTheme theme = getDefaultDarkTheme(); + + // Test 1: language badge shows current buffer language + { + StatusBarInput in; + in.language = "python"; + in.filePath = "src/main.py"; + auto model = buildStatusBar(in, theme); + assert(model.languageBadge == "python"); + std::cout << "Test 1 PASSED: language badge\n"; + passed++; + } + + // Test 2: cursor position updates + { + StatusBarInput in; + in.filePath = "file"; + in.line = 42; + in.column = 8; + auto model = buildStatusBar(in, theme); + assert(model.centerText.find("Ln 42, Col 8") != std::string::npos); + std::cout << "Test 2 PASSED: cursor position\n"; + passed++; + } + + // Test 3: diagnostic count accurate + { + StatusBarInput in; + in.filePath = "file"; + in.errorCount = 3; + in.warningCount = 2; + auto model = buildStatusBar(in, theme); + assert(model.diagnosticsText == "3 errors, 2 warnings"); + std::cout << "Test 3 PASSED: diagnostics count\n"; + passed++; + } + + // Test 4: workflow badge shown when active + { + StatusBarInput in; + in.filePath = "file"; + in.workflowActive = true; + in.workflowCurrent = 3; + in.workflowTotal = 10; + auto model = buildStatusBar(in, theme); + assert(model.workflowBadge == "Executing 3/10"); + std::cout << "Test 4 PASSED: workflow badge active\n"; + passed++; + } + + // Test 5: click diagnostics focuses diagnostics panel + { + assert(statusBarClickDiagnosticsTarget() == "diagnostics"); + std::cout << "Test 5 PASSED: diagnostics click target\n"; + passed++; + } + + // Test 6: status bar uses theme colors + { + StatusBarInput in; + in.filePath = "file"; + auto model = buildStatusBar(in, theme); + assert(model.bgColor.toHex() == theme.bgPanel.toHex()); + assert(model.textColor.toHex() == theme.text.toHex()); + assert(model.dimTextColor.toHex() == theme.textDim.toHex()); + std::cout << "Test 6 PASSED: theme colors\n"; + passed++; + } + + // Test 7: language changes when switching buffers + { + StatusBarInput py; + py.language = "python"; + py.filePath = "a.py"; + StatusBarInput cpp; + cpp.language = "cpp"; + cpp.filePath = "a.cpp"; + auto m1 = buildStatusBar(py, theme); + auto m2 = buildStatusBar(cpp, theme); + assert(m1.languageBadge != m2.languageBadge); + std::cout << "Test 7 PASSED: language switch reflected\n"; + passed++; + } + + // Test 8: encoding display + { + StatusBarInput in; + in.filePath = "file"; + in.encoding = "UTF-16"; + auto model = buildStatusBar(in, theme); + assert(model.centerText.find("UTF-16") != std::string::npos); + std::cout << "Test 8 PASSED: encoding display\n"; + passed++; + } + + // Test 9: all sections fit without overflow for normal input + { + StatusBarInput in; + in.filePath = "src/main.cpp"; + auto model = buildStatusBar(in, theme, 200); + assert(!model.overflow); + std::cout << "Test 9 PASSED: no overflow normal case\n"; + passed++; + } + + // Test 10: overflow flagged for extreme long path + { + StatusBarInput in; + in.filePath = std::string(220, 'a'); + auto model = buildStatusBar(in, theme, 80); + assert(model.overflow); + std::cout << "Test 10 PASSED: overflow flagged\n"; + passed++; + } + + // Test 11: line ending display + { + StatusBarInput in; + in.filePath = "file"; + in.lineEnding = "CRLF"; + auto model = buildStatusBar(in, theme); + assert(model.centerText.find("CRLF") != std::string::npos); + std::cout << "Test 11 PASSED: line ending display\n"; + passed++; + } + + // Test 12: MCP indicator states + { + StatusBarInput on; + on.filePath = "file"; + on.mcpConnected = true; + StatusBarInput off = on; + off.mcpConnected = false; + auto a = buildStatusBar(on, theme); + auto b = buildStatusBar(off, theme); + assert(a.mcpText == "MCP: connected"); + assert(b.mcpText == "MCP: offline"); + std::cout << "Test 12 PASSED: MCP indicator states\n"; + passed++; + } + + std::cout << "\nResults: " << passed << "/12\n"; + assert(passed == 12); + return 0; +} diff --git a/progress.md b/progress.md index 3d4531e..b3309df 100644 --- a/progress.md +++ b/progress.md @@ -1899,6 +1899,42 @@ theme-derived colors for consistent visual integration. - `step355_test` — PASS (8/8) regression coverage - `step356_test` — PASS (12/12) new step coverage +### Step 357: Status Bar +**Status:** PASS (12/12 tests) + +Added a status-bar data model that provides left/center/right sections for +language/file, cursor+encoding+line-ending, and diagnostics/workflow/MCP state. +The model includes theme-driven colors, overflow signaling, and a diagnostics +focus target for click handling. + +**Files created:** +- `editor/src/panels/StatusBar.h` — headless status model: + - `StatusBarInput` + `StatusBarModel` + - `buildStatusBar(...)` section construction and overflow check + - language badge color mapping + - diagnostics click target helper +- `editor/tests/step357_test.cpp` — 12 tests covering: + 1. language badge + 2. cursor position updates + 3. diagnostic summary counts + 4. workflow badge visibility/content + 5. diagnostics click target + 6. theme color usage + 7. language switch behavior + 8. encoding display + 9. normal-fit no-overflow case + 10. overflow flag on extreme content + 11. line-ending display + 12. MCP connected/offline indicator + +**Files modified:** +- `editor/CMakeLists.txt` — `step357_test` target + +**Verification run:** +- `step355_test` — PASS (8/8) regression coverage +- `step356_test` — PASS (12/12) regression coverage +- `step357_test` — PASS (12/12) new step coverage + # Roadmap Planning — Sprints 12-25+ ## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)