From cda8f9ae37e501efbed901fa6d97bb2047befc9c Mon Sep 17 00:00:00 2001 From: GigabiteStudios Date: Thu, 18 Jun 2026 18:55:34 -0500 Subject: [PATCH] feat(session): restore repository tabs and selection --- src/managers/user_data.cpp | 19 ++++++++++ src/managers/user_data.h | 5 +++ src/ui/gitree_ui.cpp | 72 +++++++++++++++++++++++++++++++++----- 3 files changed, 87 insertions(+), 9 deletions(-) diff --git a/src/managers/user_data.cpp b/src/managers/user_data.cpp index 083dd44..2ad78b8 100644 --- a/src/managers/user_data.cpp +++ b/src/managers/user_data.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace { std::filesystem::path roaming_directory() { @@ -49,6 +50,12 @@ void UserData::load() { if (!path.empty()) recently_closed_.push_back(path); if (recently_closed_.size() == 12) break; } + + std::ifstream session(directory_ / "session.txt"); + session >> active_repository_; + while (session >> std::quoted(path)) open_repositories_.push_back(path); + if (open_repositories_.empty()) active_repository_ = 0; + else active_repository_ = std::min(active_repository_, open_repositories_.size() - 1); } void UserData::addRecentlyClosed(const std::string& path) { @@ -59,6 +66,14 @@ void UserData::addRecentlyClosed(const std::string& path) { save(); } +void UserData::setRepositorySession(std::vector paths, size_t active_repository) { + open_repositories_ = std::move(paths); + active_repository_ = open_repositories_.empty() + ? 0 + : std::min(active_repository, open_repositories_.size() - 1); + save(); +} + void UserData::save() const { std::filesystem::create_directories(directory_); std::ofstream settings(directory_ / "settings.ini", std::ios::trunc); @@ -69,4 +84,8 @@ void UserData::save() const { std::ofstream history(directory_ / "history.txt", std::ios::trunc); for (const auto& path : recently_closed_) history << std::quoted(path) << '\n'; + + std::ofstream session(directory_ / "session.txt", std::ios::trunc); + session << active_repository_ << '\n'; + for (const auto& path : open_repositories_) session << std::quoted(path) << '\n'; } diff --git a/src/managers/user_data.h b/src/managers/user_data.h index 174738c..a5380c6 100644 --- a/src/managers/user_data.h +++ b/src/managers/user_data.h @@ -13,6 +13,8 @@ public: [[nodiscard]] const std::filesystem::path& directory() const { return directory_; } [[nodiscard]] const std::string& imguiIniPath() const { return imgui_ini_path_; } [[nodiscard]] const std::vector& recentlyClosed() const { return recently_closed_; } + [[nodiscard]] const std::vector& openRepositories() const { return open_repositories_; } + [[nodiscard]] size_t activeRepository() const { return active_repository_; } [[nodiscard]] float sidebarWidth() const { return sidebar_width_; } [[nodiscard]] float sidebarSectionHeight(size_t index) const { return sidebar_section_heights_.at(index); } [[nodiscard]] int pullMode() const { return pull_mode_; } @@ -21,6 +23,7 @@ public: void setSidebarSectionHeight(size_t index, float height) { sidebar_section_heights_.at(index) = height; } void setPullMode(int mode) { pull_mode_ = mode; save(); } void addRecentlyClosed(const std::string& path); + void setRepositorySession(std::vector paths, size_t active_repository); void save() const; private: @@ -29,6 +32,8 @@ private: std::filesystem::path directory_; std::string imgui_ini_path_; std::vector recently_closed_; + std::vector open_repositories_; + size_t active_repository_ = 0; float sidebar_width_ = 230.0f; std::array sidebar_section_heights_ = {110.0f, 220.0f, 90.0f, 150.0f}; int pull_mode_ = 1; diff --git a/src/ui/gitree_ui.cpp b/src/ui/gitree_ui.cpp index 9b206d3..814e502 100644 --- a/src/ui/gitree_ui.cpp +++ b/src/ui/gitree_ui.cpp @@ -57,9 +57,18 @@ constexpr const char* ICON_TB_TAG = "\xee\xa4\x80"; float ui(float value) { return value * g_ui_scale; } RepositoryView& repo() { return *g_tabs.at(g_active_tab); } -void create_new_tab() { +void persist_repository_session() { + if (!g_user_data || g_tabs.empty()) return; + std::vector paths; + paths.reserve(g_tabs.size()); + for (const auto& tab : g_tabs) paths.push_back(tab->path); + g_user_data->setRepositorySession(std::move(paths), g_active_tab); +} + +void create_new_tab(bool persist = true) { g_tabs.push_back(std::make_unique()); g_active_tab = g_tabs.size() - 1; + if (persist) persist_repository_session(); } void close_tab(size_t index) { @@ -69,10 +78,13 @@ void close_tab(size_t index) { if (g_tabs.empty()) create_new_tab(); else if (g_active_tab >= g_tabs.size()) g_active_tab = g_tabs.size() - 1; else if (index < g_active_tab) --g_active_tab; + persist_repository_session(); } bool open_repository(const char* path) { - return g_git_manager->openRepository(repo(), path, g_notice); + const bool opened = g_git_manager->openRepository(repo(), path, g_notice); + if (opened) persist_repository_session(); + return opened; } void pick_and_open_repository() { @@ -99,7 +111,9 @@ void pick_and_open_repository() { } bool init_repository(const char* path) { - return g_git_manager->initializeRepository(repo(), path, g_notice); + const bool initialized = g_git_manager->initializeRepository(repo(), path, g_notice); + if (initialized) persist_repository_session(); + return initialized; } void apply_style(float scale) { @@ -932,16 +946,34 @@ void draw_app() { } size_t tab_to_close = g_tabs.size(); + size_t tab_move_from = g_tabs.size(); + size_t tab_move_to = g_tabs.size(); bool add_tab = false; - if (ImGui::BeginTabBar("repositories", ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_Reorderable)) { + if (ImGui::BeginTabBar("repositories", ImGuiTabBarFlags_AutoSelectNewTabs)) { for (size_t i = 0; i < g_tabs.size(); ++i) { bool open = true; const std::string label = g_tabs[i]->name + "###repo_tab_" + std::to_string(i); - const ImGuiTabItemFlags flags = i == g_active_tab ? ImGuiTabItemFlags_SetSelected : ImGuiTabItemFlags_None; + const ImGuiTabItemFlags flags = (i == g_active_tab ? ImGuiTabItemFlags_SetSelected : ImGuiTabItemFlags_None) | + ImGuiTabItemFlags_NoReorder; if (ImGui::BeginTabItem(label.c_str(), &open, flags)) { - g_active_tab = i; + if (g_active_tab != i) { + g_active_tab = i; + persist_repository_session(); + } ImGui::EndTabItem(); } + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) { + ImGui::SetDragDropPayload("GITREE_REPOSITORY_TAB", &i, sizeof(i)); + ImGui::TextUnformatted(g_tabs[i]->name.c_str()); + ImGui::EndDragDropSource(); + } + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("GITREE_REPOSITORY_TAB")) { + tab_move_from = *static_cast(payload->Data); + tab_move_to = i; + } + ImGui::EndDragDropTarget(); + } if (ImGui::BeginPopupContextItem()) { if (!g_tabs[i]->path.empty() && ImGui::MenuItem(ICON_FA_FOLDER_OPEN " Reveal in file manager")) { std::string error; @@ -959,6 +991,17 @@ void draw_app() { if (ImGui::SmallButton(ICON_FA_PLUS "##new_tab")) add_tab = true; ImGui::EndTabBar(); } + if (tab_move_from < g_tabs.size() && tab_move_to < g_tabs.size() && tab_move_from != tab_move_to) { + RepositoryView* active = g_tabs[g_active_tab].get(); + auto moved = std::move(g_tabs[tab_move_from]); + g_tabs.erase(g_tabs.begin() + static_cast(tab_move_from)); + g_tabs.insert(g_tabs.begin() + static_cast(tab_move_to), std::move(moved)); + const auto active_tab = std::find_if(g_tabs.begin(), g_tabs.end(), [active](const auto& tab) { + return tab.get() == active; + }); + g_active_tab = static_cast(std::distance(g_tabs.begin(), active_tab)); + persist_repository_session(); + } if (tab_to_close < g_tabs.size()) close_tab(tab_to_close); if (add_tab) create_new_tab(); ImGui::Separator(); @@ -1060,7 +1103,19 @@ int runGitree(int argc, char** argv) { UserData user_data; g_user_data = &user_data; g_sidebar_width = user_data.sidebarWidth(); - create_new_tab(); + + if (argc > 1) { + create_new_tab(false); + open_repository(argv[1]); + } else if (!user_data.openRepositories().empty()) { + for (const std::string& path : user_data.openRepositories()) { + g_tabs.push_back(std::make_unique()); + if (!path.empty()) git_manager.openRepository(*g_tabs.back(), path, g_notice); + } + g_active_tab = std::min(user_data.activeRepository(), g_tabs.size() - 1); + } else { + create_new_tab(false); + } WindowManager window_manager; if (!window_manager.create("Gitree", 1500, 900)) return 1; @@ -1076,8 +1131,6 @@ int runGitree(int argc, char** argv) { ImGui_ImplGlfw_InitForOpenGL(window_manager.nativeWindow(), true); ImGui_ImplOpenGL3_Init("#version 330"); - if (argc > 1) open_repository(argv[1]); - while (!window_manager.shouldClose()) { window_manager.pollEvents(); if (window_manager.consumeDpiChange()) load_fonts(window_manager.dpiScale()); @@ -1101,6 +1154,7 @@ int runGitree(int argc, char** argv) { ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); user_data.setSidebarWidth(g_sidebar_width); + persist_repository_session(); user_data.save(); ImGui::DestroyContext(); g_window_manager = nullptr;