Step 107c: persist editor mode in recent files
This commit is contained in:
@@ -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. |
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<WelcomeAction>& a) { actions_ = a; }
|
||||
|
||||
const std::vector<RecentFile>& 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(); }
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
32
editor/tests/step107c_test.cpp
Normal file
32
editor/tests/step107c_test.cpp
Normal file
@@ -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 <cassert>
|
||||
#include <iostream>
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user