diff --git a/PROGRESS.md b/PROGRESS.md index e962c6d..124eab4 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -210,7 +210,7 @@ All 38 steps implemented and passing. Each step has a corresponding test (`step1 - [x] Step 107: **IMPLEMENTED** — Before/after diff view with preview and accept/reject (1/1 tests pass) - [x] Step 107a: **IMPLEMENTED** — Text-Editor Mode toggle and per-buffer mode tracking (2/2 tests pass) - [x] Step 107b: **IMPLEMENTED** — Mode-specific UI behavior and feature gating (2/2 tests pass) -- [ ] Step 107c: **PLANNED** — Per-buffer persistence for Text/Structured modes (not started) +- [x] Step 107c: **IMPLEMENTED** — Per-buffer mode persistence in recent files (2/2 tests pass) --- @@ -300,6 +300,7 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi **Step 107:** Compile and pass (1/1) **Step 107a:** Compile and pass (2/2) **Step 107b:** Compile and pass (2/2) +**Step 107c:** Compile and pass (2/2) --- @@ -419,3 +420,4 @@ Sprint 4 in progress. Step 76 (LayoutManager) done. Next: Step 77 (custom code e | 2026-02-09 | Codex | Step 107: Before/after diff view with preview and accept/reject. 1/1 tests pass. | | 2026-02-09 | Codex | Step 107a: Text-Editor Mode toggle and per-buffer mode tracking. 2/2 tests pass. | | 2026-02-09 | Codex | Step 107b: Mode-specific UI behavior and feature gating. 2/2 tests pass. | +| 2026-02-09 | Codex | Step 107c: Per-buffer mode persistence in recent files. 2/2 tests pass. | diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 9f1a65a..eac39a4 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -586,6 +586,10 @@ add_executable(step107b_test tests/step107b_test.cpp) target_include_directories(step107b_test PRIVATE src) target_link_libraries(step107b_test PRIVATE nlohmann_json::nlohmann_json) +add_executable(step107c_test tests/step107c_test.cpp) +target_include_directories(step107c_test PRIVATE src) +target_link_libraries(step107c_test PRIVATE nlohmann_json::nlohmann_json) + find_package(SDL2 CONFIG REQUIRED) find_package(OpenGL REQUIRED) find_package(glad CONFIG REQUIRED) diff --git a/editor/src/WelcomeScreen.h b/editor/src/WelcomeScreen.h index a5b7143..c5f4348 100644 --- a/editor/src/WelcomeScreen.h +++ b/editor/src/WelcomeScreen.h @@ -18,6 +18,7 @@ struct WelcomeAction { struct RecentFile { std::string path; std::string language; + std::string mode; std::string displayName; // just the filename }; @@ -54,18 +55,21 @@ public: void setActions(const std::vector& a) { actions_ = a; } const std::vector& getRecentFiles() const { return recentFiles_; } - void addRecentFile(const std::string& path, const std::string& lang) { + void addRecentFile(const std::string& path, const std::string& lang, const std::string& mode = "structured") { // Extract display name from path std::string name = path; auto sep = path.find_last_of("/\\"); if (sep != std::string::npos) name = path.substr(sep + 1); // Don't add duplicates - for (const auto& f : recentFiles_) { - if (f.path == path) return; + for (auto it = recentFiles_.begin(); it != recentFiles_.end(); ++it) { + if (it->path == path) { + recentFiles_.erase(it); + break; + } } // Keep max 10 recent files if (recentFiles_.size() >= 10) recentFiles_.pop_back(); - recentFiles_.insert(recentFiles_.begin(), {path, lang, name}); + recentFiles_.insert(recentFiles_.begin(), {path, lang, mode, name}); } void clearRecentFiles() { recentFiles_.clear(); } diff --git a/editor/src/main.cpp b/editor/src/main.cpp index 712d713..2f0cc28 100644 --- a/editor/src/main.cpp +++ b/editor/src/main.cpp @@ -241,7 +241,9 @@ struct EditorState { } } - void createBuffer(const std::string& path, const std::string& content, const std::string& language) { + void createBuffer(const std::string& path, const std::string& content, + const std::string& language, + BufferManager::BufferMode mode = BufferManager::BufferMode::Structured) { if (buffers.hasBuffer(path)) { buffers.switchToBuffer(path); activeBuffer = bufferStates[path].get(); @@ -262,8 +264,8 @@ struct EditorState { state->generatedHighlightsDirty = true; state->modified = false; state->lspVersion = 1; - buffers.openBuffer(path, content, language, BufferManager::BufferMode::Structured); - state->bufferMode = BufferManager::BufferMode::Structured; + buffers.openBuffer(path, content, language, mode); + state->bufferMode = mode; activeBuffer = state.get(); bufferStates[path] = std::move(state); if (path.rfind("(untitled", 0) != 0) watcher.watch(path); @@ -301,7 +303,8 @@ struct EditorState { if (!item.contains("path")) continue; std::string path = item.value("path", ""); std::string lang = item.value("language", ""); - if (!path.empty()) welcome.addRecentFile(path, lang); + std::string mode = item.value("mode", "structured"); + if (!path.empty()) welcome.addRecentFile(path, lang, mode); } } @@ -310,7 +313,7 @@ struct EditorState { std::filesystem::create_directories(p.parent_path()); nlohmann::json j = nlohmann::json::array(); for (const auto& rf : welcome.getRecentFiles()) { - j.push_back({{"path", rf.path}, {"language", rf.language}}); + j.push_back({{"path", rf.path}, {"language", rf.language}, {"mode", rf.mode}}); } std::ofstream out(p.string(), std::ios::binary); out << j.dump(2); @@ -439,7 +442,8 @@ struct EditorState { } } - void doOpen(const std::string& path) { + void doOpen(const std::string& path, + BufferManager::BufferMode modeOverride = BufferManager::BufferMode::Structured) { std::ifstream in(path); if (in.is_open()) { std::ostringstream ss; @@ -464,8 +468,8 @@ struct EditorState { language = "rust"; else if (path.size() > 3 && path.substr(path.size() - 3) == ".go") language = "go"; - createBuffer(path, content, language); - welcome.addRecentFile(path, language); + createBuffer(path, content, language, modeOverride); + welcome.addRecentFile(path, language, bufferModeToString(modeOverride)); saveRecentFiles(); outputLog += "Opened: " + path + "\n"; } else { @@ -642,6 +646,15 @@ static int countLines(const std::string& text) { return lines; } +static std::string bufferModeToString(BufferManager::BufferMode mode) { + return mode == BufferManager::BufferMode::Text ? "text" : "structured"; +} + +static BufferManager::BufferMode bufferModeFromString(const std::string& mode) { + if (mode == "text") return BufferManager::BufferMode::Text; + return BufferManager::BufferMode::Structured; +} + static std::string generateForLanguage(const Module* ast, const std::string& language) { if (!ast) return ""; if (language == "python") { @@ -1138,7 +1151,7 @@ static void RenderWelcome(WelcomeScreen& welcome, EditorState& state, std::strin ImGui::Text("Recent Files"); for (const auto& rf : welcome.getRecentFiles()) { if (ImGui::Selectable(rf.displayName.c_str())) { - state.doOpen(rf.path); + state.doOpen(rf.path, bufferModeFromString(rf.mode)); } } @@ -1368,6 +1381,12 @@ int main(int, char**) { } else { state.onTextChanged(); } + if (state.active()->path.rfind("(untitled", 0) != 0) { + state.welcome.addRecentFile(state.active()->path, + state.active()->language, + bufferModeToString(state.active()->bufferMode)); + state.saveRecentFiles(); + } } } if (ImGui::BeginMenu("Layout")) { diff --git a/editor/tests/step107c_test.cpp b/editor/tests/step107c_test.cpp new file mode 100644 index 0000000..8b97197 --- /dev/null +++ b/editor/tests/step107c_test.cpp @@ -0,0 +1,32 @@ +// Step 107c TDD Test: Recent file mode persistence +// +// Tests: +// 1. Recent files store mode string +// 2. Re-adding updates mode + +#include +#include +#include "WelcomeScreen.h" + +int main() { + int passed = 0; + int failed = 0; + + WelcomeScreen welcome; + welcome.addRecentFile("/tmp/a.py", "python", "text"); + auto recents = welcome.getRecentFiles(); + assert(recents.size() == 1); + assert(recents[0].mode == "text"); + std::cout << "Test 1 PASS: mode stored" << std::endl; + ++passed; + + welcome.addRecentFile("/tmp/a.py", "python", "structured"); + recents = welcome.getRecentFiles(); + assert(recents.size() == 1); + assert(recents[0].mode == "structured"); + std::cout << "Test 2 PASS: mode updated" << std::endl; + ++passed; + + std::cout << "\n=== Step 107c Results: " << passed << " passed, " << failed << " failed ===" << std::endl; + return failed > 0 ? 1 : 0; +}