From 8d5817d917fc0635a7f026efd24e54386bacc62b Mon Sep 17 00:00:00 2001 From: Timon Ensel Date: Mon, 21 Oct 2024 16:16:33 +0200 Subject: [PATCH 1/5] Implement Commit-Tree * Implement snappy windows * Rebuild commit list * Add drag-n-drop branch selection * Create Promotion Window * Implement scrolling * Make branch change with arrow keys possible --- src/core/CMakeLists.txt | 3 +- src/core/OSTreeTUI.cpp | 212 ++++++++++++------- src/core/OSTreeTUI.hpp | 2 +- src/core/commit.cpp | 437 +++++++++++++++++++++++++++++++++------- src/core/commit.hpp | 66 +++--- src/core/manager.cpp | 114 +---------- src/core/manager.hpp | 62 +----- src/core/scroller.cpp | 64 ------ src/core/scroller.hpp | 23 --- src/main.cpp | 4 +- 10 files changed, 539 insertions(+), 448 deletions(-) delete mode 100644 src/core/scroller.cpp delete mode 100644 src/core/scroller.hpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 6dc4b39..308483d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -10,8 +10,7 @@ add_library(ostree-tui_core commit.cpp manager.hpp OSTreeTUI.cpp OSTreeTUI.hpp - scroller.cpp - scroller.hpp) +) target_link_libraries(ostree-tui_core PRIVATE clip diff --git a/src/core/OSTreeTUI.cpp b/src/core/OSTreeTUI.cpp index 251440e..63793cb 100644 --- a/src/core/OSTreeTUI.cpp +++ b/src/core/OSTreeTUI.cpp @@ -7,17 +7,14 @@ #include #include #include - #include -#include "commit.hpp" #include "ftxui/component/component.hpp" // for Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop #include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive #include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border -#include "scroller.hpp" - +#include "commit.hpp" #include "footer.hpp" #include "manager.hpp" @@ -45,7 +42,7 @@ std::vector OSTreeTUI::parseVisibleCommitMap(cpplibostree::OSTreeRe return visibleCommitViewMap; } -int OSTreeTUI::main(const std::string& repo, const std::vector& startupBranches, bool showTooltips) { +int OSTreeTUI::main(const std::string& repo, const std::vector& startupBranches) { using namespace ftxui; // - STATES ---------- ---------- @@ -55,6 +52,7 @@ int OSTreeTUI::main(const std::string& repo, const std::vector& sta // View size_t selectedCommit{0}; // view-index std::unordered_map visibleBranches{}; // map branch visibility to branch + std::vector columnToBranchMap{}; // map column in commit-tree to branch (may be merged into one data-structure with visibleBranches) std::vector visibleCommitViewMap{}; // map from view-index to commit-hash std::unordered_map branchColorMap{}; // map branch to color std::string notificationText = ""; // footer notification @@ -76,38 +74,136 @@ int OSTreeTUI::main(const std::string& repo, const std::vector& sta } } - // - UPDATES ---------- ---------- - - auto refresh_repository = [&] { - ostreeRepo.updateData(); + // - UI ELEMENTS ---------- ---------- + auto screen = ScreenInteractive::Fullscreen(); + + std::vector allBranches = ostreeRepo.getBranches(); + + visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); // TODO This update shouldn't be made here... + + // COMMIT TREE +/* The commit-tree is currentrly under a heavy rebuild, see implementation To-Dos below. + * For a general list of To-Dos refer to https://github.com/AP-Sensing/ostree-tui/pull/21 + * + * TODO extend with keyboard functionality: + * normal scrolling through commits (should also highlight selected commit) + * if 'p' is pressed: start promotion + * if 'd' is pressed: open deletion window + * TODO add commit deletion + * add deletion button & ask for confirmation (also add keyboard functionality) + */ + // commit promotion state + // TODO especially needed for keyboard shortcuts + // store shared information about which commit is in which state + // each commit can then display itself the way it should + // * is promotion action active? + // * keyboard or mouse? + // * which commit? + // * is deletion action active? + // * keyboard or mouse? + // * which commit? + bool inPromotionSelection{false}; + bool refresh{false}; + std::string promotionHash{""}; + std::string promotionBranch{""}; + // parse all commits + Components commitComponents; + Component commitList; + Component tree; + + int scrollOffset{0}; + + auto refresh_commitComponents = [&] { + commitComponents.clear(); + int i{0}; visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); - return true; - }; - auto next_commit = [&] { - if (selectedCommit + 1 >= visibleCommitViewMap.size()) { - selectedCommit = visibleCommitViewMap.size() - 1; - return false; + for (auto& hash : visibleCommitViewMap) { + commitComponents.push_back( + CommitRender::CommitComponent(i, scrollOffset, inPromotionSelection, promotionHash, promotionBranch, visibleBranches, columnToBranchMap, hash, ostreeRepo, refresh) + ); + i++; } - ++selectedCommit; - return true; + // + commitList = commitComponents.size() == 0 + ? Renderer([&] { return text(" no commits to be shown ") | color(Color::Red); }) + : Container::Stacked(commitComponents); + // }; - auto prev_commit = [&] { - if (selectedCommit <= 0) { - selectedCommit = 0; - return false; - } - --selectedCommit; + refresh_commitComponents(); + + tree = Renderer([&] { + refresh_commitComponents(); + selectedCommit = std::min(selectedCommit, visibleCommitViewMap.size() - 1); + // TODO check for promotion & pass information if needed + if (inPromotionSelection && promotionBranch.size() != 0) { + std::unordered_map promotionBranchColorMap{}; + for (auto& [str,col] : branchColorMap) { + if (str == promotionBranch) { + promotionBranchColorMap.insert({str,col}); + } else { + promotionBranchColorMap.insert({str,Color::GrayDark}); + } + } + return CommitRender::commitRender(ostreeRepo, visibleCommitViewMap, visibleBranches, columnToBranchMap, promotionBranchColorMap, scrollOffset, selectedCommit); + } + return CommitRender::commitRender(ostreeRepo, visibleCommitViewMap, visibleBranches, columnToBranchMap, branchColorMap, scrollOffset, selectedCommit); + }); + + Component commitListComponent = Container::Horizontal({ + tree, + commitList + }); + + /// refresh all graphical components in the commit-tree + auto refresh_commitListComoponent = [&] { + commitListComponent->DetachAllChildren(); + refresh_commitComponents(); + Component tmp = Container::Horizontal({ + tree, + commitList + }); + commitListComponent->Add(tmp); + }; + /// refresh ostree-repository and graphical components + auto refresh_repository = [&] { + ostreeRepo.updateData(); + refresh_commitListComoponent(); return true; }; - // - UI ELEMENTS ---------- ---------- - auto screen = ScreenInteractive::Fullscreen(); - - std::vector allBranches = ostreeRepo.getBranches(); - + // window specific shortcuts + commitListComponent = CatchEvent(commitListComponent, [&](Event event) { + // scroll + if (event.is_mouse() && event.mouse().button == Mouse::WheelUp) { + if (scrollOffset < 0) { + ++scrollOffset; + } + selectedCommit = -scrollOffset / 4; + return true; + } + if (event.is_mouse() && event.mouse().button == Mouse::WheelDown) { + --scrollOffset; + selectedCommit = -scrollOffset / 4; + return true; + } + // switch through commits + if (event == Event::ArrowUp || event == Event::Character('k')) { + scrollOffset = std::min(0, scrollOffset + 4); + selectedCommit = -scrollOffset / 4; + return true; + } + if (event == Event::ArrowDown || event == Event::Character('j')) { + scrollOffset -= 4; + selectedCommit = -scrollOffset / 4; + return true; + } + return false; + }); + // INTERCHANGEABLE VIEW // info Component infoView = Renderer([&] { + visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); // TODO This update shouldn't be made here... if (visibleCommitViewMap.size() <= 0) { return text(" no commit info available ") | color(Color::RedLight) | bold | center; } @@ -119,36 +215,16 @@ int OSTreeTUI::main(const std::string& repo, const std::vector& sta Component filterView = Renderer(filterManager.branchBoxes, [&] { return filterManager.branchBoxRender(); }); - - // promotion - ContentPromotionManager promotionManager(showTooltips); - promotionManager.setBranchRadiobox(Radiobox(&allBranches, &promotionManager.selectedBranch)); - promotionManager.setApplyButton(Button(" Apply ", [&] { - ostreeRepo.promoteCommit(visibleCommitViewMap.at(selectedCommit), - ostreeRepo.getBranches().at(static_cast(promotionManager.selectedBranch)), - {}, promotionManager.newSubject, - true); - refresh_repository(); - notificationText = " Applied content promotion. "; - }, ButtonOption::Simple())); - Component promotionView = Renderer(promotionManager.composePromotionComponent(), [&] { - if (visibleCommitViewMap.size() <= 0) { - return text(" please select a commit to continue commit-promotion... ") | color(Color::RedLight) | bold | center; - } - return promotionManager.renderPromotionView(ostreeRepo, screen.dimy(), - ostreeRepo.getCommitList().at(visibleCommitViewMap.at(selectedCommit))); - }); + filterView = CatchEvent(filterView, [&](Event event) { + if (event.is_mouse() && event.mouse().button == Mouse::Button::Left) { + refresh_commitListComoponent(); + } + return false; + }); // interchangeable view (composed) - Manager manager(infoView, filterView, promotionView); + Manager manager(infoView, filterView); Component managerRenderer = manager.managerRenderer; - - // COMMIT TREE - Component logRenderer = Scroller(&selectedCommit, CommitRender::COMMIT_DETAIL_LEVEL, Renderer([&] { - visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); - selectedCommit = std::min(selectedCommit, visibleCommitViewMap.size() - 1); - return CommitRender::commitRender(ostreeRepo, visibleCommitViewMap, visibleBranches, branchColorMap, selectedCommit); - })); // FOOTER Footer footer; @@ -156,28 +232,22 @@ int OSTreeTUI::main(const std::string& repo, const std::vector& sta return footer.footerRender(); }); - // window specific shortcuts - logRenderer = CatchEvent(logRenderer, [&](Event event) { - // switch through commits - if (event == Event::ArrowUp || event == Event::Character('k') || (event.is_mouse() && event.mouse().button == Mouse::WheelUp)) { - return prev_commit(); - } - if (event == Event::ArrowDown || event == Event::Character('j') || (event.is_mouse() && event.mouse().button == Mouse::WheelDown)) { - return next_commit(); - } - return false; - }); - + // build together all components int logSize{45}; int footerSize{1}; Component container{managerRenderer}; - container = ResizableSplitLeft(logRenderer, container, &logSize); + container = ResizableSplitLeft(commitListComponent, container, &logSize); container = ResizableSplitBottom(footerRenderer, container, &footerSize); - logRenderer->TakeFocus(); + commitListComponent->TakeFocus(); // add application shortcuts Component mainContainer = CatchEvent(container | border, [&](const Event& event) { + //if (event == Event::Character('p')) { + // inPromotionSelection = true; + // promotionHash = visibleCommitViewMap.at(selectedCommit); + // promotionBranch = columnToBranchMap.at(0); + //} // copy commit id if (event == Event::AltC) { std::string hash = visibleCommitViewMap.at(selectedCommit); @@ -186,9 +256,10 @@ int OSTreeTUI::main(const std::string& repo, const std::vector& sta return true; } // refresh repository - if (event == Event::AltR) { + if (event == Event::AltR || refresh) { refresh_repository(); notificationText = " Refreshed Repository Data "; + refresh = false; return true; } // exit @@ -236,7 +307,6 @@ int OSTreeTUI::showHelp(const std::string& caller, const std::string& errorMessa // option, arguments, meaning {"-h, --help", "", "Show help options. The REPOSITORY_PATH can be omitted"}, {"-r, --refs", "REF [REF...]", "Specify a list of visible refs at startup if not specified, show all refs"}, - {"-n, --no-tooltips", "", "Hide Tooltips in promotion view."} }; Elements options {text("Options:")}; diff --git a/src/core/OSTreeTUI.hpp b/src/core/OSTreeTUI.hpp index 9ae549b..d032fae 100644 --- a/src/core/OSTreeTUI.hpp +++ b/src/core/OSTreeTUI.hpp @@ -15,7 +15,7 @@ namespace OSTreeTUI { * * @param repo ostree repository path */ - int main(const std::string& repo, const std::vector& startupBranches = {}, bool showTooltips = true); + int main(const std::string& repo, const std::vector& startupBranches = {}); /** * @brief Print help page diff --git a/src/core/commit.cpp b/src/core/commit.cpp index f7e0fa0..514a887 100644 --- a/src/core/commit.cpp +++ b/src/core/commit.cpp @@ -4,28 +4,365 @@ #include #include #include +#include -#include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border +#include // for Component, ComponentBase +#include "ftxui/component/component.hpp"// for Make +#include // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp +#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::WheelDown, Mouse::WheelUp +#include "ftxui/dom/deprecated.hpp" // for text +#include "ftxui/dom/elements.hpp" // for operator|, Element, size, vbox, EQUAL, HEIGHT, dbox, reflect, focus, inverted, nothing, select, vscroll_indicator, yflex, yframe +#include "ftxui/dom/node.hpp" // for Node +#include "ftxui/dom/requirement.hpp" // for Requirement +#include "ftxui/screen/box.hpp" // for Box +#include +#include // for ScreenInteractive +#include "ftxui/dom/elements.hpp" // for text, window, hbox, vbox, size, clear_under, reflect, emptyElement +#include "ftxui/screen/color.hpp" // for Color +#include "ftxui/screen/screen.hpp" // for Screen #include "../util/cpplibostree.hpp" namespace CommitRender { -void addLine(const RenderTree& treeLineType, const RenderLine& lineType, - ftxui::Elements& treeElements, ftxui::Elements& commElements, - const cpplibostree::Commit& commit, - const bool& highlight, - const std::unordered_map& usedBranches, - const std::unordered_map& branchColorMap) { - treeElements.push_back(addTreeLine(treeLineType, commit, usedBranches, branchColorMap)); - commElements.push_back(addCommLine(lineType, commit, highlight, branchColorMap)); +namespace { +using namespace ftxui; + +/// From https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp +Decorator PositionAndSize(int left, int top, int width, int height) { + return [=](Element element) { + element |= size(WIDTH, EQUAL, width); + element |= size(HEIGHT, EQUAL, height); + + auto padding_left = emptyElement() | size(WIDTH, EQUAL, left); + auto padding_top = emptyElement() | size(HEIGHT, EQUAL, top); + + return vbox({ + padding_top, + hbox({ + padding_left, + element, + }), + }); + }; +} + +/// From https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp +Element DefaultRenderState(const WindowRenderState& state) { + Element element = state.inner; + if (!state.active) { + element |= dim; + } + + element = window(text(state.title), element); + element |= clear_under; + + return element; +} + +/// Draggable commit window, including ostree-tui logic for overlap detection, etc. +/// Partially inspired from https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp +class CommitComponentImpl : public ComponentBase, public WindowOptions { + public: + explicit CommitComponentImpl(int position, + int& scrollOffset, + bool& inPromotionSelection, + std::string& promotionHash, + std::string& promotionBranch, + std::unordered_map& visibleBranches, + std::vector& columnToBranchMap, + std::string commit, + cpplibostree::OSTreeRepo& ostreerepo, + bool& refresh) : + scrollOffset(scrollOffset), + inPromotionSelection(inPromotionSelection), + promotionHash(promotionHash), + promotionBranch(promotionBranch), + visibleBranches(visibleBranches), + columnToBranchMap(columnToBranchMap), + hash(commit), + ostreerepo(ostreerepo), + refresh(refresh) { + inner = Renderer([&] { + return vbox({ + text(ostreerepo.getCommitList().at(hash).subject), + text(std::format("{:%Y-%m-%d %T %Ez}", std::chrono::time_point_cast(ostreerepo.getCommitList().at(hash).timestamp))), + }); + }); + simpleCommit = inner; + Add(inner); + + title = hash.substr(0, 8); + top = position * 4; + left = 1; + width = 30; + height = 4; + } + + private: + void resetWindow() { + left() = drag_initial_x; + top() = drag_initial_y; + width() = width_initial; + height() = height_initial; + // reset window contents + DetachAllChildren(); + Add(simpleCommit); + } + void startPromotionWindow() { + left() = std::max(left(),-2); + width() = width_initial + 8; + height() = height_initial + 10; + // change inner to promotion layout + simpleCommit = inner; + DetachAllChildren(); + Add(promotionView); + } + + Element Render() final { + auto element = ComponentBase::Render(); + + const WindowRenderState state = { + element, + title(), + Active(), + drag_ + }; + + element = render ? render(state) : DefaultRenderState(state); + + // Position and record the drawn area of the window. + element |= reflect(box_window_); + element |= PositionAndSize(left(), top() + scrollOffset, width(), height()); + element |= reflect(box_); + + return element; + } + + bool OnEvent(Event event) final { + if (ComponentBase::OnEvent(event)) { + return true; + } + + if (inPromotionSelection) { + if (event == Event::ArrowLeft) { + int it = std::find(columnToBranchMap.begin(), columnToBranchMap.end(), promotionBranch) - columnToBranchMap.begin(); + promotionBranch = columnToBranchMap.at((it - 1) % columnToBranchMap.size()); + return true; + } + if (event == Event::ArrowRight) { + int it = std::find(columnToBranchMap.begin(), columnToBranchMap.end(), promotionBranch) - columnToBranchMap.begin(); + promotionBranch = columnToBranchMap.at((it + 1) % columnToBranchMap.size()); + return true; + } + if (event == Event::Return) { + ostreerepo.promoteCommit(hash, promotionBranch, {}, newSubject, true); + resetWindow(); + inPromotionSelection = false; + refresh = true; + return true; + } + } + + if (!event.is_mouse()) { + return false; + } + + if (inPromotionSelection && ! drag_) { + return true; + } + + mouse_hover_ = box_window_.Contain(event.mouse().x, event.mouse().y); + + if (mouse_hover_) { + // TODO indicate window is draggable? + } + + if (captured_mouse_) { + if (event.mouse().motion == Mouse::Released) { + // reset mouse + captured_mouse_ = nullptr; + // check if position matches branch & do something if it does + if (promotionBranch.size() != 0) { + // initiate promotion + inPromotionSelection = true; + promotionHash = hash; + startPromotionWindow(); + } else { + // not promotion + inPromotionSelection = false; + promotionBranch = ""; + resetWindow(); + } + return true; + } + + if (drag_) { + left() = event.mouse().x - drag_start_x - box_.x_min; + top() = event.mouse().y - drag_start_y - box_.y_min; + // promotion + inPromotionSelection = true; + // calculate which branch currently is hovered over + promotionBranch = ""; + int branch_pos = event.mouse().x / 2; + int count{0}; + for (auto& [branch,visible] : visibleBranches) { + if (visible) { + ++count; + } + if (count == branch_pos) { + // problem -> branch sorting not stored anywhere... + // promotionBranch = branch; + promotionBranch = columnToBranchMap.at(event.mouse().x / 2 - 1); + break; + } + } + } else { + // not promotion + inPromotionSelection = false; + promotionBranch = ""; + } + + // Clamp the window size. + width() = std::max(width(), static_cast(title().size() + 2)); + height() = std::max(height(), 2); + + return true; + } + + if (!mouse_hover_) { + return false; + } + + if (!CaptureMouse(event)) { + return true; + } + + if (event.mouse().button != Mouse::Left) { + return true; + } + if (event.mouse().motion != Mouse::Pressed) { + return true; + } + + TakeFocus(); + + captured_mouse_ = CaptureMouse(event); + if (!captured_mouse_) { + return true; + } + + drag_start_x = event.mouse().x - left() - box_.x_min; + drag_start_y = event.mouse().y - top() - box_.y_min; + + bool drag_old = drag_; + drag_ = true; + if (!drag_old && drag_) { // if we start dragging + drag_initial_x = left(); + drag_initial_y = top(); + } + return true; + } + + // window specific members + Box box_; + Box box_window_; + + CapturedMouse captured_mouse_; + int drag_start_x = 0; + int drag_start_y = 0; + + int drag_initial_x = 0; + int drag_initial_y = 0; + int width_initial = 30; + int height_initial = 4; + + bool mouse_hover_ = false; + bool drag_ = false; + + int& scrollOffset; + + // ostree-tui specific members + // TODO store commit data + std::string hash; + cpplibostree::OSTreeRepo& ostreerepo; + std::unordered_map& visibleBranches; + std::vector& columnToBranchMap; + + bool& refresh; + + // common / shared variables to set the promotion state + bool& inPromotionSelection; + std::string& promotionHash; + std::string& promotionBranch; + + // promotion view + std::string newSubject; + Component simpleCommit = Renderer([] { + return text("error in commit window creation"); + }); + Component promotionView = Container::Vertical({ + Renderer([&] { + return vbox({ + text(""), + text(" Promote Commit...") | bold, + text(""), + text(" ☐ todocommithash") | bold, + }); + }), + Container::Horizontal({ + Renderer([&] { + return text(" ┆ subject: "); + }), + Input(&newSubject, "enter new subject...") | underlined + }), + // TODO render other metadata strings, if available (like version?) + Renderer([&] { + return vbox({ + text(" ┆"), + text(" ┆ to branch:"), + text(" ☐ " + promotionBranch) | bold, + text(" │") | bold + }); + }), + Container::Horizontal({ + Button(" Cancel [n] ", [&] { + inPromotionSelection = false; + resetWindow(); + }) | color(Color::Red) | flex, + Button(" Promote [y] ", [&] { + inPromotionSelection = false; + // promote on the ostree repo + ostreerepo.promoteCommit(hash, promotionBranch, {}, newSubject, true); + resetWindow(); + refresh = true; + }) | color(Color::Green) | flex, + }) + }); +}; + +} // namespace + +ftxui::Component CommitComponent(int position, + int& scrollOffset, + bool& inPromotionSelection, + std::string& promotionHash, + std::string& promotionBranch, + std::unordered_map& visibleBranches, + std::vector& columnToBranchMap, + std::string commit, + cpplibostree::OSTreeRepo& ostreerepo, + bool& refresh) { + return ftxui::Make(position, scrollOffset, inPromotionSelection, promotionHash, promotionBranch, visibleBranches, columnToBranchMap, commit, ostreerepo, refresh); } ftxui::Element commitRender(cpplibostree::OSTreeRepo& repo, const std::vector& visibleCommitMap, const std::unordered_map& visibleBranches, - const std::unordered_map& branchColorMap, - size_t selectedCommit) { + std::vector& columnToBranchMap, + const std::unordered_map& branchColorMap, + int scrollOffset, + size_t selectedCommit) { using namespace ftxui; // check empty commit list @@ -48,24 +385,29 @@ ftxui::Element commitRender(cpplibostree::OSTreeRepo& repo, Elements commElements{}; std::string markedString = repo.getCommitList().at(visibleCommitMap.at(selectedCommit)).hash; + columnToBranchMap.clear(); for (const auto& visibleCommitIndex : visibleCommitMap) { cpplibostree::Commit commit = repo.getCommitList().at(visibleCommitIndex); - bool highlight = markedString == commit.hash; // branch head if it is first branch usage std::string relevantBranch = commit.branch; if (usedBranches.at(relevantBranch) == -1) { + columnToBranchMap.push_back(relevantBranch); usedBranches.at(relevantBranch) = nextAvailableSpace--; - addLine(RenderTree::TREE_LINE_IGNORE_BRANCH, RenderLine::BRANCH_HEAD, - treeElements, commElements, commit, highlight, usedBranches, branchColorMap); + // TODO somehow incorporate the branch name into the commit-tree + //addLine(RenderTree::TREE_LINE_IGNORE_BRANCH, RenderLine::BRANCH_HEAD, + // treeElements, commElements, commit, highlight, usedBranches, branchColorMap); } // commit - addLine(RenderTree::TREE_LINE_NODE, RenderLine::COMMIT_HASH, - treeElements, commElements, commit, highlight, usedBranches, branchColorMap); - addLine(RenderTree::TREE_LINE_TREE, RenderLine::COMMIT_DATE, - treeElements, commElements, commit, highlight, usedBranches, branchColorMap); - addLine(RenderTree::TREE_LINE_TREE, RenderLine::EMPTY, - treeElements, commElements, commit, highlight, usedBranches, branchColorMap); + if (scrollOffset++ >= 0) { + treeElements.push_back(addTreeLine(RenderTree::TREE_LINE_NODE, commit, usedBranches, branchColorMap)); + } + for (int i{0}; i < 3; i++) { + if (scrollOffset++ >= 0) { + treeElements.push_back(addTreeLine(RenderTree::TREE_LINE_TREE, commit, usedBranches, branchColorMap)); + } + } } + std::reverse(columnToBranchMap.begin(), columnToBranchMap.end()); return hbox({ vbox(std::move(treeElements)), @@ -105,59 +447,4 @@ ftxui::Element addTreeLine(const RenderTree& treeLineType, return hbox(std::move(tree)); } - -ftxui::Element addCommLine(RenderLine lineType, - const cpplibostree::Commit& commit, - const bool& highlight, - const std::unordered_map& branchColorMap) { - using namespace ftxui; - - std::string relevantBranch = commit.branch; - Elements comm; - - switch (lineType) { - case EMPTY: { - comm.push_back(text("")); - break; - } - case BRANCH_HEAD: { - comm.push_back(text(relevantBranch) | color(branchColorMap.at(relevantBranch))); - break; - } - case COMMIT_HASH: { - // length adapted hash - std::string commitTopText = commit.hash; - if (commitTopText.size() > 8) { - commitTopText = GAP_TREE_COMMITS + commit.hash.substr(0, 8); - } - Element commitTopTextElement = text(commitTopText); - // highlighted / selected - if (highlight) { - commitTopTextElement = commitTopTextElement | bold | inverted; - } - // signed - if (cpplibostree::OSTreeRepo::isCommitSigned(commit)) { - std::string signedText = " signed " + (commit.signatures.size() > 1 ? std::to_string(commit.signatures.size()) + "x" : ""); - commitTopTextElement = hbox(commitTopTextElement, text(signedText) | color(Color::Green)); - } - comm.push_back(commitTopTextElement); - break; - } - case COMMIT_DATE: { - std::string ts = std::format("{:%Y-%m-%d %T %Ez}", - std::chrono::time_point_cast(commit.timestamp)); - comm.push_back(text(GAP_TREE_COMMITS + ts)); - break; - } - case COMMIT_SUBJ: { - std::string ts = std::format("{:%Y-%m-%d %T %Ez}", - std::chrono::time_point_cast(commit.timestamp)); - comm.push_back(paragraph(GAP_TREE_COMMITS + commit.subject)); - break; - } - } - - return hbox(std::move(comm)); -} - } // namespace CommitRender diff --git a/src/core/commit.hpp b/src/core/commit.hpp index f4aa2bc..49c20e8 100644 --- a/src/core/commit.hpp +++ b/src/core/commit.hpp @@ -11,15 +11,18 @@ #pragma once -#include #include #include +#include +#include +#include "ftxui/component/component_base.hpp" + #include "../util/cpplibostree.hpp" namespace CommitRender { - constexpr size_t COMMIT_DETAIL_LEVEL {3}; // lines per commit + constexpr size_t COMMIT_DETAIL_LEVEL {4}; // lines per commit constexpr std::string COMMIT_NODE {" ☐"}; constexpr std::string COMMIT_TREE {" │"}; @@ -41,6 +44,27 @@ namespace CommitRender { COMMIT_SUBJ // Some Subject }; + /** + * @brief Creates a window, containing a hash and some of its details. + * The window has a pre-defined position and snaps back to it, + * after being dragged & let go. When hovering over a branch, + * defined in `columnToBranchMap`, the window expands to a commit + * promotion window. + * To be used with other windows, use a ftxui::Component::Stacked. + * + * @return Component + */ + ftxui::Component CommitComponent(int position, + int& scrollOffset, + bool& inPromotionSelection, + std::string& promotionHash, + std::string& promotionBranch, + std::unordered_map& visibleBranches, + std::vector& columnToBranchMap, + std::string commit, + cpplibostree::OSTreeRepo& ostreerepo, + bool& refresh); + /** * @brief create a Renderer for the commit section * @@ -54,29 +78,11 @@ namespace CommitRender { ftxui::Element commitRender(cpplibostree::OSTreeRepo& repo, const std::vector& visibleCommitMap, const std::unordered_map& visibleBranches, + std::vector& columnToBranchMap, const std::unordered_map& branchColorMap, + int scrollOffset, size_t selectedCommit = 0); - /** - * @brief Add a line to a commit-tree-column and commit-info-column. - * Both are built and set using addTreeLine() and addTreeLine(). - * - * @param treeLineType type of commit_tree - * @param lineType type of commit_info (e.g. hash, date,...) - * @param treeElements commit tree column - * @param commElements commit info column - * @param commit commit to render / get info from - * @param highlight should commit be highlighted (as selected) - * @param usedBranches branches to render - * @param branchColorMap branch colors - */ - void addLine(const RenderTree& treeLineType, const RenderLine& lineType, - ftxui::Elements& treeElements, ftxui::Elements& commElements, - const cpplibostree::Commit& commit, - const bool& highlight, - const std::unordered_map& usedBranches, - const std::unordered_map& branchColorMap); - /** * @brief build a commit-tree line * @@ -90,19 +96,5 @@ namespace CommitRender { const cpplibostree::Commit& commit, const std::unordered_map& usedBranches, const std::unordered_map& branchColorMap); - - /** - * @brief build a commit-info line - * - * @param lineType type of commit-info - * @param commit commit to render / get info from - * @param highlight should commit be highlighted (as selected) - * @param branchColorMap branch colors - * @return ftxui::Element commit-info line - */ - ftxui::Element addCommLine(RenderLine lineType, - const cpplibostree::Commit& commit, - const bool& highlight, - const std::unordered_map& branchColorMap); - + } // namespace CommitRender diff --git a/src/core/manager.cpp b/src/core/manager.cpp index a40e294..0a835b7 100644 --- a/src/core/manager.cpp +++ b/src/core/manager.cpp @@ -12,15 +12,14 @@ // Manager -Manager::Manager(const ftxui::Component& infoView, const ftxui::Component& filterView, const ftxui::Component& promotionView) { +Manager::Manager(const ftxui::Component& infoView, const ftxui::Component& filterView) { using namespace ftxui; tabSelection = Menu(&tab_entries, &tab_index, MenuOption::HorizontalAnimated()); tabContent = Container::Tab({ infoView, - filterView, - promotionView + filterView }, &tab_index); @@ -102,112 +101,3 @@ ftxui::Element CommitInfoManager::renderInfoView(const cpplibostree::Commit& dis filler() }); } - -// ContentPromotionManager - -ContentPromotionManager::ContentPromotionManager(bool show_tooltips): show_tooltips(show_tooltips) { - using namespace ftxui; - - subjectComponent = Input(&newSubject, "subject"); -} - -void ContentPromotionManager::setBranchRadiobox(ftxui::Component radiobox) { - using namespace ftxui; - - branchSelection = CatchEvent(radiobox, [&](const Event& event) { - // copy commit id - if (event == Event::Return) { - subjectComponent->TakeFocus(); - } - return false; - }); -} - -void ContentPromotionManager::setApplyButton(ftxui::Component button) { - applyButton = button; -} - -ftxui::Elements ContentPromotionManager::renderPromotionCommand(cpplibostree::OSTreeRepo& ostreeRepo, const std::string& selectedCommitHash) { - using namespace ftxui; - - assert(branchSelection); - assert(applyButton); - - Elements line; - line.push_back(text("ostree commit") | bold); - line.push_back(text(" --repo=" + ostreeRepo.getRepoPath()) | bold); - line.push_back(text(" -b " + ostreeRepo.getBranches().at(static_cast(selectedBranch))) | bold); - line.push_back(text(" --keep-metadata") | bold); - // optional subject - if (!newSubject.empty()) { - line.push_back(text(" -s \"") | bold); - line.push_back(text(newSubject) | color(Color::BlueLight) | bold); - line.push_back(text("\"") | bold); - } - // commit - line.push_back(text(" --tree=ref=" + selectedCommitHash) | bold); - - return line; -} - -ftxui::Component ContentPromotionManager::composePromotionComponent() { - using namespace ftxui; - - return Container::Vertical({ - branchSelection, - Container::Vertical({ - subjectComponent, - applyButton, - }), - }); -} - -ftxui::Element ContentPromotionManager::renderPromotionView(cpplibostree::OSTreeRepo& ostreeRepo, int screenHeight, const cpplibostree::Commit& displayCommit) { - using namespace ftxui; - - assert(branchSelection); - assert(applyButton); - - // compute screen element sizes - int screenOverhead {8}; // borders, footer, etc. - int commitWinHeight {3}; - int apsectWinHeight {8}; - int tooltipsWinHeight {2}; - int branchSelectWinHeight = screenHeight - screenOverhead - commitWinHeight - apsectWinHeight - tooltipsWinHeight; - // tooltips only get shown, if the window is sufficiently large - if (branchSelectWinHeight < 4) { - tooltipsWinHeight = 0; - branchSelectWinHeight = 4; - } - - // build elements - auto commitHashElem = vbox({text(" Commit: ") | bold | color(Color::Green), text(" " + displayCommit.hash)}) | flex; - auto branchWin = window(text("New Branch"), branchSelection->Render() | vscroll_indicator | frame); - auto subjectWin = window(text("Subject"), subjectComponent->Render()) | flex; - auto applyButtonWin = applyButton->Render() | color(Color::Green) | size(WIDTH, GREATER_THAN, 9) | flex; - - auto toolTipContent = [&](size_t tip) { - return vbox({ - separatorCharacter("⎯"), - text(" 🛈 " + tool_tip_strings.at(tip)), - }); - }; - auto toolTipsWin = !show_tooltips || tooltipsWinHeight < 2 ? filler() : // only show if screen is reasonable size - branchSelection->Focused() ? toolTipContent(0) : - subjectComponent->Focused() ? toolTipContent(1) : - applyButton->Focused() ? toolTipContent(2) : - filler(); - - // build element composition - return vbox({ - commitHashElem | size(HEIGHT, EQUAL, commitWinHeight), - branchWin | size(HEIGHT, LESS_THAN, branchSelectWinHeight), - vbox({ - subjectWin, - applyButtonWin, - }) | flex | size(HEIGHT, LESS_THAN, apsectWinHeight), - hflow(renderPromotionCommand(ostreeRepo, displayCommit.hash)) | flex_grow, - filler(), - toolTipsWin, - }) | flex_grow; -} diff --git a/src/core/manager.hpp b/src/core/manager.hpp index 4246214..becfa22 100644 --- a/src/core/manager.hpp +++ b/src/core/manager.hpp @@ -19,7 +19,7 @@ class Manager { int tab_index{0}; std::vector tab_entries = { - " Info ", " Filter ", " Promote " + " Info ", " Filter " }; ftxui::Component tabSelection; @@ -30,7 +30,7 @@ class Manager { ftxui::Component managerRenderer; public: - Manager(const ftxui::Component& infoView, const ftxui::Component& filterView, const ftxui::Component& promotionView); + Manager(const ftxui::Component& infoView, const ftxui::Component& filterView); }; class CommitInfoManager { @@ -58,61 +58,3 @@ class BranchBoxManager { */ ftxui::Element branchBoxRender(); }; - -class ContentPromotionManager { -public: - // branch selection - ftxui::Component branchSelection; // must be set from OSTreeTUI - int selectedBranch{0}; - - // subject - ftxui::Component subjectComponent; - - std::string newSubject{""}; - - // apply button - ftxui::Component applyButton; // must be set from OSTreeTUI - - // tool-tips - bool show_tooltips{true}; - ftxui::Component tool_tips_comp; - const std::vector tool_tip_strings = { - "Branch to promote the Commit to.", - "New subject for promoted Commit (optional).", - "Apply the Commit Promotion (write to repository).", - }; - -public: - /** - * @brief Constructor for the Promotion Manager. - * - * @warning The branchSelection and applyButton have to be set - * using the respective set-methods AFTER construction, as they - * have to be constructed in the OSTreeTUI::main - */ - ContentPromotionManager(bool show_tooltips = true); - - /// Setter - void setBranchRadiobox(ftxui::Component radiobox); - /// Setter - void setApplyButton(ftxui::Component button); - - /** - * @brief Build the promotion view Component - * - * @warning branchSelection & applyButton have to be set first (checked through assert) - * @return ftxui::Component - */ - ftxui::Component composePromotionComponent(); - - /// renders the promotion command resulting from the current user settings (ostree commit ...) - ftxui::Elements renderPromotionCommand(cpplibostree::OSTreeRepo& ostreeRepo, const std::string& selectedCommitHash); - - /** - * @brief Build the promotion view Element - * - * @warning branchSelection & applyButton have to be set first (checked through assert) - * @return ftxui::Element - */ - ftxui::Element renderPromotionView(cpplibostree::OSTreeRepo& ostreeRepo, int screenHeight, const cpplibostree::Commit& displayCommit); -}; diff --git a/src/core/scroller.cpp b/src/core/scroller.cpp deleted file mode 100644 index 71e8b17..0000000 --- a/src/core/scroller.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "scroller.hpp" - -#include // for max, min -#include // for Component, ComponentBase -#include // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp -#include // for move - -#include "ftxui/component/component.hpp" // for Make -#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::WheelDown, Mouse::WheelUp -#include "ftxui/dom/deprecated.hpp" // for text -#include "ftxui/dom/elements.hpp" // for operator|, Element, size, vbox, EQUAL, HEIGHT, dbox, reflect, focus, inverted, nothing, select, vscroll_indicator, yflex, yframe -#include "ftxui/dom/node.hpp" // for Node -#include "ftxui/dom/requirement.hpp" // for Requirement -#include "ftxui/screen/box.hpp" // for Box - - -/* - * This Scroller Element is a modified version of the Scroller in the following repository - * Title: git-tui - * Author: Arthur Sonzogni - * Date: 2021 - * Availability: https://github.com/ArthurSonzogni/git-tui/blob/master/src/scroller.cpp - */ -namespace ftxui { - -class ScrollerBase : public ComponentBase { - public: - ScrollerBase(size_t *selectedCommit, size_t elementLength, Component child): - sc(selectedCommit), - elLength(elementLength) { - Add(child); - } - - private: - size_t *sc{nullptr}; - size_t elLength{1}; - - Element Render() final { - auto focused = Focused() ? focus : ftxui::select; - - Element background = ComponentBase::Render(); - background->ComputeRequirement(); - size_ = background->requirement().min_y; - return dbox({ - std::move(background), - vbox({ - // TODO change *4 for dynamic height, or make height stay the same - text(L"") | size(HEIGHT, EQUAL, static_cast(*sc * elLength)), - text(L"") | focused, - }), - }) | vscroll_indicator | yframe | yflex | reflect(box_); - } - - bool Focusable() const final { return true; } - - int size_ = 0; - Box box_{}; -}; - -Component Scroller(size_t *selectedCommit, size_t elementLength, Component child) { - return Make(selectedCommit, elementLength, std::move(child)); -} - -} // namespace ftxui diff --git a/src/core/scroller.hpp b/src/core/scroller.hpp deleted file mode 100644 index cdd29f3..0000000 --- a/src/core/scroller.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef SCROLLER_H -#define SCROLLER_H - -#include - -#include "ftxui/component/component_base.hpp" // for Component - -namespace ftxui { - /** - * @brief This Scroller Element is a modified version of the Scroller in the following repository: - * Title: git-tui - * Author: Arthur Sonzogni - * Date: 2021 - * Availability: https://github.com/ArthurSonzogni/git-tui/blob/master/src/scroller.cpp - * - * @param selectedCommit - * @param child - * @return Component - */ - Component Scroller(size_t *selectedCommit, size_t elementLength, Component child); - -} // namespace ftxui -#endif /* end of include guard: SCROLLER_H */ diff --git a/src/main.cpp b/src/main.cpp index 1210dcb..0990bbd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -54,9 +54,7 @@ int main(int argc, const char** argv) { std::string repo = args.at(0); // -r, --refs std::vector startupBranches = getArgOptions(args, {"-r", "--refs"}); - // -n, --no-tooltips - bool hideTooltips = argExists(args, "-n") || argExists(args, "--no-tooltips"); // OSTree TUI - return OSTreeTUI::main(repo, startupBranches, !hideTooltips); + return OSTreeTUI::main(repo, startupBranches); } From be035c8d8ae85f6b7018be58b0bc98c295ffa1fe Mon Sep 17 00:00:00 2001 From: Timon Ensel Date: Mon, 4 Nov 2024 18:08:16 +0100 Subject: [PATCH 2/5] Refactor OSTreeTUI::main -> class --- src/core/OSTreeTUI.cpp | 302 ++++++++++-------- src/core/OSTreeTUI.hpp | 127 +++++++- src/core/commit.cpp | 628 ++++++++++++++++++-------------------- src/core/commit.hpp | 66 ++-- src/core/footer.cpp | 4 + src/core/footer.hpp | 12 +- src/core/manager.cpp | 16 +- src/core/manager.hpp | 12 +- src/main.cpp | 3 +- src/util/cpplibostree.cpp | 8 +- src/util/cpplibostree.hpp | 7 +- 11 files changed, 657 insertions(+), 528 deletions(-) diff --git a/src/core/OSTreeTUI.cpp b/src/core/OSTreeTUI.cpp index 63793cb..2d905a9 100644 --- a/src/core/OSTreeTUI.cpp +++ b/src/core/OSTreeTUI.cpp @@ -7,56 +7,24 @@ #include #include #include +#include #include #include "ftxui/component/component.hpp" // for Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop #include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive #include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border - -#include "commit.hpp" -#include "footer.hpp" -#include "manager.hpp" +#include // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp #include "clip.h" #include "../util/cpplibostree.hpp" -std::vector OSTreeTUI::parseVisibleCommitMap(cpplibostree::OSTreeRepo& repo, - std::unordered_map& visibleBranches) { - - std::vector visibleCommitViewMap{}; - // get filtered commits - visibleCommitViewMap = {}; - for (const auto& commitPair : repo.getCommitList()) { - if (visibleBranches[commitPair.second.branch]) { - visibleCommitViewMap.push_back(commitPair.first); - } - } - // sort by date - std::sort(visibleCommitViewMap.begin(), visibleCommitViewMap.end(), [&](const std::string& a, const std::string& b) { - return repo.getCommitList().at(a).timestamp - > repo.getCommitList().at(b).timestamp; - }); +OSTreeTUI::OSTreeTUI (const std::string& repo, const std::vector startupBranches): + ostreeRepo(repo), selectedCommit(0), screen(ftxui::ScreenInteractive::Fullscreen()) +{ + using namespace ftxui; - return visibleCommitViewMap; -} - -int OSTreeTUI::main(const std::string& repo, const std::vector& startupBranches) { - using namespace ftxui; - - // - STATES ---------- ---------- - - // Model - cpplibostree::OSTreeRepo ostreeRepo(repo); - // View - size_t selectedCommit{0}; // view-index - std::unordered_map visibleBranches{}; // map branch visibility to branch - std::vector columnToBranchMap{}; // map column in commit-tree to branch (may be merged into one data-structure with visibleBranches) - std::vector visibleCommitViewMap{}; // map from view-index to commit-hash - std::unordered_map branchColorMap{}; // map branch to color - std::string notificationText = ""; // footer notification - // set all branches as visible and define a branch color for (const auto& branch : ostreeRepo.getBranches()) { // if startupBranches are defined, set all as non-visible @@ -67,68 +35,23 @@ int OSTreeTUI::main(const std::string& repo, const std::vector& sta // if startupBranches are defined, only set those visible if (startupBranches.size() != 0) { for (const auto& branch : startupBranches) { - if (visibleBranches.find(branch) == visibleBranches.end()) { - return showHelp("ostree-tui","no such branch in repository " + repo + ": " + branch); - } visibleBranches[branch] = true; } } // - UI ELEMENTS ---------- ---------- - auto screen = ScreenInteractive::Fullscreen(); - - std::vector allBranches = ostreeRepo.getBranches(); visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); // TODO This update shouldn't be made here... // COMMIT TREE -/* The commit-tree is currentrly under a heavy rebuild, see implementation To-Dos below. - * For a general list of To-Dos refer to https://github.com/AP-Sensing/ostree-tui/pull/21 - * - * TODO extend with keyboard functionality: - * normal scrolling through commits (should also highlight selected commit) - * if 'p' is pressed: start promotion - * if 'd' is pressed: open deletion window - * TODO add commit deletion - * add deletion button & ask for confirmation (also add keyboard functionality) - */ - // commit promotion state - // TODO especially needed for keyboard shortcuts - // store shared information about which commit is in which state - // each commit can then display itself the way it should - // * is promotion action active? - // * keyboard or mouse? - // * which commit? - // * is deletion action active? - // * keyboard or mouse? - // * which commit? - bool inPromotionSelection{false}; - bool refresh{false}; - std::string promotionHash{""}; - std::string promotionBranch{""}; - // parse all commits - Components commitComponents; - Component commitList; - Component tree; + /* The commit-tree is currentrly under a heavy rebuild, see implementation To-Dos below. + * For a general list of To-Dos refer to https://github.com/AP-Sensing/ostree-tui/pull/21 + * + * TODO bug fixes: + * > keyboard functionality (especially in-app navigation) + */ - int scrollOffset{0}; - - auto refresh_commitComponents = [&] { - commitComponents.clear(); - int i{0}; - visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); - for (auto& hash : visibleCommitViewMap) { - commitComponents.push_back( - CommitRender::CommitComponent(i, scrollOffset, inPromotionSelection, promotionHash, promotionBranch, visibleBranches, columnToBranchMap, hash, ostreeRepo, refresh) - ); - i++; - } - // - commitList = commitComponents.size() == 0 - ? Renderer([&] { return text(" no commits to be shown ") | color(Color::Red); }) - : Container::Stacked(commitComponents); - // - }; + // parse all commits refresh_commitComponents(); tree = Renderer([&] { @@ -144,33 +67,16 @@ int OSTreeTUI::main(const std::string& repo, const std::vector& sta promotionBranchColorMap.insert({str,Color::GrayDark}); } } - return CommitRender::commitRender(ostreeRepo, visibleCommitViewMap, visibleBranches, columnToBranchMap, promotionBranchColorMap, scrollOffset, selectedCommit); + return CommitRender::commitRender(*this, promotionBranchColorMap, selectedCommit); } - return CommitRender::commitRender(ostreeRepo, visibleCommitViewMap, visibleBranches, columnToBranchMap, branchColorMap, scrollOffset, selectedCommit); + return CommitRender::commitRender(*this, branchColorMap, selectedCommit); }); - Component commitListComponent = Container::Horizontal({ + commitListComponent = Container::Horizontal({ tree, commitList }); - /// refresh all graphical components in the commit-tree - auto refresh_commitListComoponent = [&] { - commitListComponent->DetachAllChildren(); - refresh_commitComponents(); - Component tmp = Container::Horizontal({ - tree, - commitList - }); - commitListComponent->Add(tmp); - }; - /// refresh ostree-repository and graphical components - auto refresh_repository = [&] { - ostreeRepo.updateData(); - refresh_commitListComoponent(); - return true; - }; - // window specific shortcuts commitListComponent = CatchEvent(commitListComponent, [&](Event event) { // scroll @@ -187,12 +93,12 @@ int OSTreeTUI::main(const std::string& repo, const std::vector& sta return true; } // switch through commits - if (event == Event::ArrowUp || event == Event::Character('k')) { + if (event == Event::ArrowUp) { scrollOffset = std::min(0, scrollOffset + 4); selectedCommit = -scrollOffset / 4; return true; } - if (event == Event::ArrowDown || event == Event::Character('j')) { + if (event == Event::ArrowDown) { scrollOffset -= 4; selectedCommit = -scrollOffset / 4; return true; @@ -202,7 +108,7 @@ int OSTreeTUI::main(const std::string& repo, const std::vector& sta // INTERCHANGEABLE VIEW // info - Component infoView = Renderer([&] { + infoView = Renderer([&] { visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); // TODO This update shouldn't be made here... if (visibleCommitViewMap.size() <= 0) { return text(" no commit info available ") | color(Color::RedLight) | bold | center; @@ -211,9 +117,9 @@ int OSTreeTUI::main(const std::string& repo, const std::vector& sta }); // filter - BranchBoxManager filterManager(ostreeRepo, visibleBranches); - Component filterView = Renderer(filterManager.branchBoxes, [&] { - return filterManager.branchBoxRender(); + filterManager = std::unique_ptr(new BranchBoxManager(ostreeRepo, visibleBranches)); + filterView = Renderer(filterManager->branchBoxes, [&] { + return filterManager->branchBoxRender(); }); filterView = CatchEvent(filterView, [&](Event event) { if (event.is_mouse() && event.mouse().button == Mouse::Button::Left) { @@ -223,31 +129,26 @@ int OSTreeTUI::main(const std::string& repo, const std::vector& sta }); // interchangeable view (composed) - Manager manager(infoView, filterView); - Component managerRenderer = manager.managerRenderer; + manager = std::unique_ptr(new Manager(*this, infoView, filterView)); + managerRenderer = manager->managerRenderer; // FOOTER - Footer footer; - Component footerRenderer = Renderer([&] { + footerRenderer = Renderer([&] { return footer.footerRender(); }); // build together all components - int logSize{45}; - int footerSize{1}; - Component container{managerRenderer}; + container = Component(managerRenderer); container = ResizableSplitLeft(commitListComponent, container, &logSize); container = ResizableSplitBottom(footerRenderer, container, &footerSize); commitListComponent->TakeFocus(); // add application shortcuts - Component mainContainer = CatchEvent(container | border, [&](const Event& event) { - //if (event == Event::Character('p')) { - // inPromotionSelection = true; - // promotionHash = visibleCommitViewMap.at(selectedCommit); - // promotionBranch = columnToBranchMap.at(0); - //} + mainContainer = CatchEvent(container | border, [&](const Event& event) { + if (event == Event::AltP) { + setPromotionMode(true, visibleCommitViewMap.at(selectedCommit)); + } // copy commit id if (event == Event::AltC) { std::string hash = visibleCommitViewMap.at(selectedCommit); @@ -256,20 +157,22 @@ int OSTreeTUI::main(const std::string& repo, const std::vector& sta return true; } // refresh repository - if (event == Event::AltR || refresh) { + if (event == Event::AltR) { refresh_repository(); notificationText = " Refreshed Repository Data "; - refresh = false; return true; } // exit - if (event == Event::AltQ || event == Event::Escape) { + if (event == Event::AltQ) { screen.ExitLoopClosure()(); return true; } return false; }); +} +int OSTreeTUI::run() { + using namespace ftxui; // footer notification update loader // Probably not the best solution, having an active wait and should maybe // only be started, once a notification is set... @@ -279,7 +182,7 @@ int OSTreeTUI::main(const std::string& repo, const std::vector& sta using namespace std::chrono_literals; // notification is set if (notificationText != "") { - footer.content = notificationText; + footer.setContent(notificationText); screen.Post(Event::Custom); std::this_thread::sleep_for(2s); // clear notification @@ -298,7 +201,140 @@ int OSTreeTUI::main(const std::string& repo, const std::vector& sta return EXIT_SUCCESS; } +void OSTreeTUI::refresh_commitComponents() { + using namespace ftxui; + + commitComponents.clear(); + int i{0}; + visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); + for (auto& hash : visibleCommitViewMap) { + commitComponents.push_back( + CommitRender::CommitComponent(i, hash, *this) + ); + i++; + } + + commitList = commitComponents.size() == 0 + ? Renderer([&] { return text(" no commits to be shown ") | color(Color::Red); }) + : Container::Stacked(commitComponents); +} + +void OSTreeTUI::refresh_commitListComoponent() { + using namespace ftxui; + + commitListComponent->DetachAllChildren(); + refresh_commitComponents(); + Component tmp = Container::Horizontal({ + tree, + commitList + }); + commitListComponent->Add(tmp); +} + +bool OSTreeTUI::refresh_repository() { + ostreeRepo.updateData(); + refresh_commitListComoponent(); + return true; +} + +bool OSTreeTUI::setPromotionMode(bool active, std::string hash) { + // deactivate promotion mode + if (!active) { + inPromotionSelection = false; + promotionBranch = ""; + promotionHash = ""; + return true; + } + // set promotion mode + if (!inPromotionSelection || hash != promotionHash) { + inPromotionSelection = true; + promotionBranch = promotionBranch.empty() ? columnToBranchMap.at(0) : promotionBranch; + promotionHash = hash; + return true; + } + // nothing to update + return false; +} + +bool OSTreeTUI::promoteCommit(std::string hash, std::string branch, std::vector metadataStrings, std::string newSubject, bool keepMetadata) { + bool success = ostreeRepo.promoteCommit(hash, branch, metadataStrings, newSubject, keepMetadata); + setPromotionMode(false); + // reload repository + if (success) { + scrollOffset = 0; + selectedCommit = 0; + screen.PostEvent(ftxui::Event::AltR); + } + return success; +} + +std::vector OSTreeTUI::parseVisibleCommitMap(cpplibostree::OSTreeRepo& repo, + std::unordered_map& visibleBranches) { + + std::vector visibleCommitViewMap{}; + // get filtered commits + visibleCommitViewMap = {}; + for (const auto& commitPair : repo.getCommitList()) { + if (visibleBranches[commitPair.second.branch]) { + visibleCommitViewMap.push_back(commitPair.first); + } + } + // sort by date + std::sort(visibleCommitViewMap.begin(), visibleCommitViewMap.end(), [&](const std::string& a, const std::string& b) { + return repo.getCommitList().at(a).timestamp + > repo.getCommitList().at(b).timestamp; + }); + + return visibleCommitViewMap; +} + +// SETTER & non-const GETTER +void OSTreeTUI::setPromotionBranch(std::string promotionBranch) { + this->promotionBranch = promotionBranch; +} + +std::vector& OSTreeTUI::getColumnToBranchMap() { + return columnToBranchMap; +} + +// GETTER +const cpplibostree::OSTreeRepo& OSTreeTUI::getOstreeRepo() const { + return ostreeRepo; +} + +const std::string& OSTreeTUI::getPromotionBranch() const { + return promotionBranch; +} + +const std::unordered_map& OSTreeTUI::getVisibleBranches() const { + return visibleBranches; +} + +const std::vector& OSTreeTUI::getColumnToBranchMap() const { + return columnToBranchMap; +} + +const std::vector& OSTreeTUI::getVisibleCommitViewMap() const { + return visibleCommitViewMap; +} + +const std::unordered_map& OSTreeTUI::getBranchColorMap() const { + return branchColorMap; +} + +const int& OSTreeTUI::getScrollOffset() const { + return scrollOffset; +} + +const bool& OSTreeTUI::getInPromotionSelection() const { + return inPromotionSelection; +} + +const std::string& OSTreeTUI::getPromotionHash() const { + return promotionHash; +} +// STATIC int OSTreeTUI::showHelp(const std::string& caller, const std::string& errorMessage) { using namespace ftxui; diff --git a/src/core/OSTreeTUI.hpp b/src/core/OSTreeTUI.hpp index d032fae..b1b2187 100644 --- a/src/core/OSTreeTUI.hpp +++ b/src/core/OSTreeTUI.hpp @@ -3,20 +3,129 @@ | A terminal user interface for OSTree. |___________________________________________________________*/ +#pragma once + +#include #include #include +#include "ftxui/component/component.hpp" // for Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop +#include "ftxui/component/component_base.hpp" // for ComponentBase +#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive +#include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border + +#include "commit.hpp" +#include "footer.hpp" +#include "manager.hpp" + #include "../util/cpplibostree.hpp" -namespace OSTreeTUI { +class OSTreeTUI { +public: + /** + * @brief Constructs, builds and assembles all components of the OSTreeTUI. + * + * @param repo ostree repository (OSTreeRepo) + * @param startupBranches optional list of branches to pre-select at startup (providing nothing will display all branches) + */ + explicit OSTreeTUI (const std::string& repo, const std::vector startupBranches = {}); + + /** + * @brief Runs the OSTreeTUI (starts the ftxui screen loop). + * + * @return exit code + */ + int run(); + + /// @brief Refresh Level 3: Refreshes the commit components + void refresh_commitComponents(); + + /// @brief Refresh Level 2: Refreshes the commit list component & upper levels + void refresh_commitListComoponent(); + + /// @brief Refresh Level 1: Refreshes complete repository & upper levels + bool refresh_repository(); /** - * @brief OSTree TUI main + * @brief sets the promotion mode + * @param active activate (true), or deactivate (false) promotion mode + * @param hash provide if setting mode to true + * @return false, if other promotion gets overwritten + */ + bool setPromotionMode(bool active, std::string hash = ""); + + /** @brief promote a commit + * @param hash + * @param branch + * @return promotion success + */ + bool promoteCommit(std::string hash, std::string branch, std::vector metadataStrings = {}, std::string newSubject = "", bool keepMetadata = true); + +private: + /** + * @brief Calculates all visible commits from an OSTreeRepo and a list of branches. * - * @param repo ostree repository path + * @param repo OSTreeRepo + * @param visibleBranches Map: branch name -> visible + * @return Complete list of commit hashes in repo, that are part of the given branches */ - int main(const std::string& repo, const std::vector& startupBranches = {}); + std::vector parseVisibleCommitMap(cpplibostree::OSTreeRepo& repo, std::unordered_map& visibleBranches); + +public: + // "SETTER" & non-const GETTER + void setPromotionBranch(std::string promotionBranch); + std::vector& getColumnToBranchMap(); + + // GETTER + const cpplibostree::OSTreeRepo& getOstreeRepo() const; + const std::string& getPromotionBranch() const; + const std::unordered_map& getVisibleBranches() const; + const std::vector& getColumnToBranchMap() const; + const std::vector& getVisibleCommitViewMap() const; + const std::unordered_map& getBranchColorMap() const; + const int& getScrollOffset() const; + const bool& getInPromotionSelection() const; + const std::string& getPromotionHash() const; + +private: + // model + cpplibostree::OSTreeRepo ostreeRepo; + // backend states + size_t selectedCommit; + std::unordered_map visibleBranches; // map branch -> visibe + std::vector columnToBranchMap; // map branch -> column in commit-tree (may be merged into one data-structure with visibleBranches) + std::vector visibleCommitViewMap; // map view-index -> commit-hash + std::unordered_map branchColorMap; // map branch -> color + std::string notificationText; // footer notification + + // view states + int scrollOffset{0}; + bool inPromotionSelection{false}; + std::string promotionHash; + std::string promotionBranch; + + // view constants + int logSize{45}; + int footerSize{1}; + + // components + Footer footer; + std::unique_ptr filterManager{nullptr}; + std::unique_ptr manager{nullptr}; + ftxui::ScreenInteractive screen; + ftxui::Component mainContainer; + ftxui::Components commitComponents; + ftxui::Component commitList; + ftxui::Component tree; + ftxui::Component commitListComponent; + ftxui::Component infoView; + ftxui::Component filterView; + ftxui::Component managerRenderer; + ftxui::Component footerRenderer; + ftxui::Component container; + +public: /** * @brief Print help page * @@ -25,16 +134,12 @@ namespace OSTreeTUI { * @return 0, if no error message provided * @return 1, if error message is provided, assuming bad program stop */ - int showHelp(const std::string& caller, const std::string& errorMessage = ""); + static int showHelp(const std::string& caller, const std::string& errorMessage = ""); /** * @brief Print the application version * * @return int */ - int showVersion(); - - std::vector parseVisibleCommitMap(cpplibostree::OSTreeRepo& repo, - std::unordered_map& visibleBranches); - -} // namespace OSTreeTUI + static int showVersion(); +}; diff --git a/src/core/commit.cpp b/src/core/commit.cpp index 514a887..b9fadcd 100644 --- a/src/core/commit.cpp +++ b/src/core/commit.cpp @@ -1,28 +1,29 @@ #include "commit.hpp" +#include +#include #include +#include #include #include +#include #include -#include #include // for Component, ComponentBase #include "ftxui/component/component.hpp"// for Make #include // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp #include "ftxui/component/mouse.hpp" // for Mouse, Mouse::WheelDown, Mouse::WheelUp -#include "ftxui/dom/deprecated.hpp" // for text #include "ftxui/dom/elements.hpp" // for operator|, Element, size, vbox, EQUAL, HEIGHT, dbox, reflect, focus, inverted, nothing, select, vscroll_indicator, yflex, yframe #include "ftxui/dom/node.hpp" // for Node -#include "ftxui/dom/requirement.hpp" // for Requirement #include "ftxui/screen/box.hpp" // for Box #include #include // for ScreenInteractive -#include "ftxui/dom/elements.hpp" // for text, window, hbox, vbox, size, clear_under, reflect, emptyElement #include "ftxui/screen/color.hpp" // for Color -#include "ftxui/screen/screen.hpp" // for Screen #include "../util/cpplibostree.hpp" +#include "OSTreeTUI.hpp" + namespace CommitRender { namespace { @@ -30,372 +31,353 @@ using namespace ftxui; /// From https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp Decorator PositionAndSize(int left, int top, int width, int height) { - return [=](Element element) { - element |= size(WIDTH, EQUAL, width); - element |= size(HEIGHT, EQUAL, height); - - auto padding_left = emptyElement() | size(WIDTH, EQUAL, left); - auto padding_top = emptyElement() | size(HEIGHT, EQUAL, top); - - return vbox({ - padding_top, - hbox({ - padding_left, - element, - }), - }); - }; + return [=](Element element) { + element |= size(WIDTH, EQUAL, width); + element |= size(HEIGHT, EQUAL, height); + + auto padding_left = emptyElement() | size(WIDTH, EQUAL, left); + auto padding_top = emptyElement() | size(HEIGHT, EQUAL, top); + + return vbox({ + padding_top, + hbox({ + padding_left, + element, + }), + }); + }; } -/// From https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp +/// Partially inspired from https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp Element DefaultRenderState(const WindowRenderState& state) { - Element element = state.inner; - if (!state.active) { - element |= dim; - } + Element element = state.inner; + //if (!state.active) { + // element |= dim; + //} - element = window(text(state.title), element); - element |= clear_under; + element = window(text(state.title), element); + element |= clear_under; - return element; + return element; } /// Draggable commit window, including ostree-tui logic for overlap detection, etc. /// Partially inspired from https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp class CommitComponentImpl : public ComponentBase, public WindowOptions { - public: - explicit CommitComponentImpl(int position, - int& scrollOffset, - bool& inPromotionSelection, - std::string& promotionHash, - std::string& promotionBranch, - std::unordered_map& visibleBranches, - std::vector& columnToBranchMap, - std::string commit, - cpplibostree::OSTreeRepo& ostreerepo, - bool& refresh) : - scrollOffset(scrollOffset), - inPromotionSelection(inPromotionSelection), - promotionHash(promotionHash), - promotionBranch(promotionBranch), - visibleBranches(visibleBranches), - columnToBranchMap(columnToBranchMap), - hash(commit), - ostreerepo(ostreerepo), - refresh(refresh) { - inner = Renderer([&] { - return vbox({ - text(ostreerepo.getCommitList().at(hash).subject), - text(std::format("{:%Y-%m-%d %T %Ez}", std::chrono::time_point_cast(ostreerepo.getCommitList().at(hash).timestamp))), - }); - }); - simpleCommit = inner; - Add(inner); - - title = hash.substr(0, 8); - top = position * 4; - left = 1; - width = 30; - height = 4; - } - - private: - void resetWindow() { - left() = drag_initial_x; - top() = drag_initial_y; - width() = width_initial; - height() = height_initial; - // reset window contents - DetachAllChildren(); - Add(simpleCommit); - } - void startPromotionWindow() { - left() = std::max(left(),-2); - width() = width_initial + 8; - height() = height_initial + 10; - // change inner to promotion layout - simpleCommit = inner; - DetachAllChildren(); - Add(promotionView); - } - - Element Render() final { - auto element = ComponentBase::Render(); - - const WindowRenderState state = { - element, - title(), - Active(), - drag_ - }; - - element = render ? render(state) : DefaultRenderState(state); - - // Position and record the drawn area of the window. - element |= reflect(box_window_); - element |= PositionAndSize(left(), top() + scrollOffset, width(), height()); - element |= reflect(box_); - - return element; - } - - bool OnEvent(Event event) final { - if (ComponentBase::OnEvent(event)) { - return true; - } +public: + explicit CommitComponentImpl(int position, std::string commit, OSTreeTUI& ostreetui) : + hash(std::move(commit)), + ostreetui(ostreetui), + commit(ostreetui.getOstreeRepo().getCommitList().at(hash)), + newVersion(this->commit.version), + drag_initial_y(position * COMMIT_WINDOW_HEIGHT), + drag_initial_x(1) + { + inner = Renderer([&] { + return vbox({ + text(ostreetui.getOstreeRepo().getCommitList().at(hash).subject), + text(std::format("{:%Y-%m-%d %T %Ez}", std::chrono::time_point_cast(ostreetui.getOstreeRepo().getCommitList().at(hash).timestamp))), + }); + }); + simpleCommit = inner; + Add(inner); + + title = hash.substr(0, 8); + top = drag_initial_y; + left = drag_initial_x; + width = COMMIT_WINDOW_WIDTH; + height = COMMIT_WINDOW_HEIGHT; + } - if (inPromotionSelection) { - if (event == Event::ArrowLeft) { - int it = std::find(columnToBranchMap.begin(), columnToBranchMap.end(), promotionBranch) - columnToBranchMap.begin(); - promotionBranch = columnToBranchMap.at((it - 1) % columnToBranchMap.size()); - return true; - } - if (event == Event::ArrowRight) { - int it = std::find(columnToBranchMap.begin(), columnToBranchMap.end(), promotionBranch) - columnToBranchMap.begin(); - promotionBranch = columnToBranchMap.at((it + 1) % columnToBranchMap.size()); - return true; - } - if (event == Event::Return) { - ostreerepo.promoteCommit(hash, promotionBranch, {}, newSubject, true); - resetWindow(); - inPromotionSelection = false; - refresh = true; - return true; +private: + void resetWindow() { + left() = drag_initial_x; + top() = drag_initial_y; + width() = width_initial; + height() = height_initial; + // reset window contents + DetachAllChildren(); + Add(simpleCommit); + } + + void startPromotionWindow() { + left() = std::max(left(),-2); + width() = PROMOTION_WINDOW_WIDTH; + height() = PROMOTION_WINDOW_HEIGHT; + // change inner to promotion layout + simpleCommit = inner; + DetachAllChildren(); + Add(promotionView); + TakeFocus(); + } + + void executePromotion() { + // promote on the ostree repo + std::vector metadataStrings; + if (! newVersion.empty()) { + metadataStrings.push_back("version=" + newVersion); } + ostreetui.promoteCommit(hash, ostreetui.getPromotionBranch(), metadataStrings, newSubject, true); + resetWindow(); } - if (!event.is_mouse()) { - return false; - } + void cancelPromotion() { + ostreetui.setPromotionMode(false); + resetWindow(); + } - if (inPromotionSelection && ! drag_) { - return true; - } + Element Render() final { + // check if promotion was started not from drag & drop, but from ostreetui + if (ostreetui.getInPromotionSelection() && ostreetui.getPromotionHash() == hash) { + startPromotionWindow(); + } - mouse_hover_ = box_window_.Contain(event.mouse().x, event.mouse().y); + auto element = ComponentBase::Render(); - if (mouse_hover_) { - // TODO indicate window is draggable? - } + const WindowRenderState state = { + element, + title(), + Active(), + drag_ + }; - if (captured_mouse_) { - if (event.mouse().motion == Mouse::Released) { - // reset mouse - captured_mouse_ = nullptr; - // check if position matches branch & do something if it does - if (promotionBranch.size() != 0) { - // initiate promotion - inPromotionSelection = true; - promotionHash = hash; - startPromotionWindow(); - } else { - // not promotion - inPromotionSelection = false; - promotionBranch = ""; - resetWindow(); - } - return true; - } - - if (drag_) { - left() = event.mouse().x - drag_start_x - box_.x_min; - top() = event.mouse().y - drag_start_y - box_.y_min; - // promotion - inPromotionSelection = true; - // calculate which branch currently is hovered over - promotionBranch = ""; - int branch_pos = event.mouse().x / 2; - int count{0}; - for (auto& [branch,visible] : visibleBranches) { - if (visible) { - ++count; - } - if (count == branch_pos) { - // problem -> branch sorting not stored anywhere... - // promotionBranch = branch; - promotionBranch = columnToBranchMap.at(event.mouse().x / 2 - 1); - break; - } - } - } else { - // not promotion - inPromotionSelection = false; - promotionBranch = ""; - } - - // Clamp the window size. - width() = std::max(width(), static_cast(title().size() + 2)); - height() = std::max(height(), 2); - - return true; - } + element = render ? render(state) : DefaultRenderState(state); - if (!mouse_hover_) { - return false; - } + // Position and record the drawn area of the window. + element |= reflect(box_window_); + element |= PositionAndSize(left(), top() + ostreetui.getScrollOffset(), width(), height()); + element |= reflect(box_); - if (!CaptureMouse(event)) { - return true; - } + return element; + } - if (event.mouse().button != Mouse::Left) { - return true; - } - if (event.mouse().motion != Mouse::Pressed) { - return true; - } + bool OnEvent(Event event) final { + if (ComponentBase::OnEvent(event)) { + return true; + } + + if (ostreetui.getInPromotionSelection()) { + // navigate promotion branches + if (event == Event::ArrowLeft) { + const long int it = std::find(ostreetui.getColumnToBranchMap().begin(), ostreetui.getColumnToBranchMap().end(), ostreetui.getPromotionBranch()) - ostreetui.getColumnToBranchMap().begin(); + ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at((it - 1) % ostreetui.getColumnToBranchMap().size())); + return true; + } + if (event == Event::ArrowRight) { + const long int it = std::find(ostreetui.getColumnToBranchMap().begin(), ostreetui.getColumnToBranchMap().end(), ostreetui.getPromotionBranch()) - ostreetui.getColumnToBranchMap().begin(); + ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at((it + 1) % ostreetui.getColumnToBranchMap().size())); + return true; + } + // promote + if (event == Event::Return) { + executePromotion(); + return true; + } + // cancel + if (event == Event::Escape) { + cancelPromotion(); + return true; + } + } - TakeFocus(); + if (!event.is_mouse()) { + return false; + } + + if (ostreetui.getInPromotionSelection() && ! drag_) { + return true; + } + + mouse_hover_ = box_window_.Contain(event.mouse().x, event.mouse().y); + // potentially indicate mouse hover + //if (box_window_.Contain(event.mouse().x, event.mouse().y)) {} + + if (captured_mouse_) { + if (event.mouse().motion == Mouse::Released) { + // reset mouse + captured_mouse_ = nullptr; + // check if position matches branch & do something if it does + if (ostreetui.getPromotionBranch().empty()) { + ostreetui.setPromotionMode(false, hash); + resetWindow(); + } else { + ostreetui.setPromotionMode(true, hash); + } + return true; + } + + if (drag_) { + left() = event.mouse().x - drag_start_x - box_.x_min; + top() = event.mouse().y - drag_start_y - box_.y_min; + // promotion + ostreetui.setPromotionMode(true, hash); // TODO switch to ostreetui call + // calculate which branch currently is hovered over + ostreetui.setPromotionBranch(""); + const int branch_pos = event.mouse().x / 2; + int count{0}; + for (const auto& [branch,visible] : ostreetui.getVisibleBranches()) { + if (visible) { + ++count; + } + if (count == branch_pos) { + ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at(event.mouse().x / 2 - 1)); + break; + } + } + } else { + // not promotion + ostreetui.setPromotionMode(false, hash); + } + + // Clamp the window size. + width() = std::max(width(), static_cast(title().size() + 2)); + height() = std::max(height(), 2); + + return true; + } + + if (!mouse_hover_) { + return false; + } + + if (!CaptureMouse(event)) { + return true; + } + + if (event.mouse().button != Mouse::Left) { + return true; + } + if (event.mouse().motion != Mouse::Pressed) { + return true; + } + + TakeFocus(); + + captured_mouse_ = CaptureMouse(event); + if (!captured_mouse_) { + return true; + } + + drag_start_x = event.mouse().x - left() - box_.x_min; + drag_start_y = event.mouse().y - top() - box_.y_min; + + const bool drag_old = drag_; + drag_ = true; + if (!drag_old && drag_) { // if we start dragging + drag_initial_x = left(); + drag_initial_y = top(); + } + return true; + } - captured_mouse_ = CaptureMouse(event); - if (!captured_mouse_) { - return true; - } + // window specific members + Box box_; + Box box_window_; - drag_start_x = event.mouse().x - left() - box_.x_min; - drag_start_y = event.mouse().y - top() - box_.y_min; + CapturedMouse captured_mouse_; + int drag_start_x = 0; + int drag_start_y = 0; - bool drag_old = drag_; - drag_ = true; - if (!drag_old && drag_) { // if we start dragging - drag_initial_x = left(); - drag_initial_y = top(); - } - return true; - } - - // window specific members - Box box_; - Box box_window_; - - CapturedMouse captured_mouse_; - int drag_start_x = 0; - int drag_start_y = 0; - - int drag_initial_x = 0; - int drag_initial_y = 0; - int width_initial = 30; - int height_initial = 4; - - bool mouse_hover_ = false; - bool drag_ = false; - - int& scrollOffset; - - // ostree-tui specific members - // TODO store commit data - std::string hash; - cpplibostree::OSTreeRepo& ostreerepo; - std::unordered_map& visibleBranches; - std::vector& columnToBranchMap; - - bool& refresh; - - // common / shared variables to set the promotion state - bool& inPromotionSelection; - std::string& promotionHash; - std::string& promotionBranch; - - // promotion view - std::string newSubject; - Component simpleCommit = Renderer([] { - return text("error in commit window creation"); - }); - Component promotionView = Container::Vertical({ - Renderer([&] { - return vbox({ - text(""), - text(" Promote Commit...") | bold, - text(""), - text(" ☐ todocommithash") | bold, - }); - }), - Container::Horizontal({ - Renderer([&] { - return text(" ┆ subject: "); - }), - Input(&newSubject, "enter new subject...") | underlined - }), - // TODO render other metadata strings, if available (like version?) - Renderer([&] { - return vbox({ - text(" ┆"), - text(" ┆ to branch:"), - text(" ☐ " + promotionBranch) | bold, - text(" │") | bold - }); - }), - Container::Horizontal({ - Button(" Cancel [n] ", [&] { - inPromotionSelection = false; - resetWindow(); - }) | color(Color::Red) | flex, - Button(" Promote [y] ", [&] { - inPromotionSelection = false; - // promote on the ostree repo - ostreerepo.promoteCommit(hash, promotionBranch, {}, newSubject, true); - resetWindow(); - refresh = true; - }) | color(Color::Green) | flex, - }) - }); + int drag_initial_x; + int drag_initial_y; + int width_initial = COMMIT_WINDOW_WIDTH; + int height_initial = COMMIT_WINDOW_HEIGHT; + + bool mouse_hover_ = false; + bool drag_ = false; + + // ostree-tui specific members + std::string hash; + OSTreeTUI& ostreetui; + + // promotion view + cpplibostree::Commit commit; + std::string newSubject; + std::string newVersion; + Component simpleCommit = Renderer([] { + return text("error in commit window creation"); + }); + Component promotionView = Container::Vertical({ + Renderer([&] { + return vbox({ + text(""), + text(" Promote Commit...") | bold, + text(""), + text(" ☐ todocommithash") | bold, + }); + }), + Container::Horizontal({ + Renderer([&] { + return text(" ┆ subject: "); + }), + Input(&newSubject, "enter new subject...") | underlined + }), + // render version, if available + commit.version.empty() + ? Renderer([]{return filler();}) + : Container::Horizontal({ + Renderer([&] { + return text(" ┆ version: "); + }), + Input(&newVersion, commit.version) | underlined + }), + Renderer([&] { + return vbox({ + text(" ┆"), + text(" ┆ to branch:"), + text(" ☐ " + ostreetui.getPromotionBranch()) | bold, + text(" │") | bold + }); + }), + Container::Horizontal({ + Button(" Cancel ", [&] { + cancelPromotion(); + }) | color(Color::Red) | flex, + Button(" Promote ", [&] { + executePromotion(); + }) | color(Color::Green) | flex, + }) + }); }; } // namespace -ftxui::Component CommitComponent(int position, - int& scrollOffset, - bool& inPromotionSelection, - std::string& promotionHash, - std::string& promotionBranch, - std::unordered_map& visibleBranches, - std::vector& columnToBranchMap, - std::string commit, - cpplibostree::OSTreeRepo& ostreerepo, - bool& refresh) { - return ftxui::Make(position, scrollOffset, inPromotionSelection, promotionHash, promotionBranch, visibleBranches, columnToBranchMap, commit, ostreerepo, refresh); +ftxui::Component CommitComponent(int position, const std::string& commit, OSTreeTUI& ostreetui) { + return ftxui::Make(position, commit, ostreetui); } -ftxui::Element commitRender(cpplibostree::OSTreeRepo& repo, - const std::vector& visibleCommitMap, - const std::unordered_map& visibleBranches, - std::vector& columnToBranchMap, - const std::unordered_map& branchColorMap, - int scrollOffset, - size_t selectedCommit) { +ftxui::Element commitRender(OSTreeTUI& ostreetui, const std::unordered_map& branchColorMap, size_t selectedCommit) { using namespace ftxui; + int scrollOffset = ostreetui.getScrollOffset(); + // check empty commit list - if (visibleCommitMap.size() <= 0 || visibleBranches.size() <= 0) { + if (ostreetui.getVisibleCommitViewMap().empty() || ostreetui.getVisibleBranches().empty()) { return color(Color::RedLight, text(" no commits to be shown ") | bold | center); } // stores the dedicated tree-column of each branch, -1 meaning not displayed yet std::unordered_map usedBranches{}; - for (const auto& branchPair : visibleBranches) { + for (const auto& branchPair : ostreetui.getVisibleBranches()) { if (branchPair.second) { usedBranches[branchPair.first] = -1; } } - int nextAvailableSpace = usedBranches.size() - 1; + int nextAvailableSpace = static_cast(usedBranches.size() - 1); // - RENDER - // left tree, right commits Elements treeElements{}; Elements commElements{}; - std::string markedString = repo.getCommitList().at(visibleCommitMap.at(selectedCommit)).hash; - columnToBranchMap.clear(); - for (const auto& visibleCommitIndex : visibleCommitMap) { - cpplibostree::Commit commit = repo.getCommitList().at(visibleCommitIndex); + ostreetui.getColumnToBranchMap().clear(); + for (const auto& visibleCommitIndex : ostreetui.getVisibleCommitViewMap()) { + const cpplibostree::Commit commit = ostreetui.getOstreeRepo().getCommitList().at(visibleCommitIndex); // branch head if it is first branch usage - std::string relevantBranch = commit.branch; + const std::string relevantBranch = commit.branch; if (usedBranches.at(relevantBranch) == -1) { - columnToBranchMap.push_back(relevantBranch); + ostreetui.getColumnToBranchMap().push_back(relevantBranch); usedBranches.at(relevantBranch) = nextAvailableSpace--; - // TODO somehow incorporate the branch name into the commit-tree - //addLine(RenderTree::TREE_LINE_IGNORE_BRANCH, RenderLine::BRANCH_HEAD, - // treeElements, commElements, commit, highlight, usedBranches, branchColorMap); } // commit if (scrollOffset++ >= 0) { @@ -407,7 +389,7 @@ ftxui::Element commitRender(cpplibostree::OSTreeRepo& repo, } } } - std::reverse(columnToBranchMap.begin(), columnToBranchMap.end()); + std::reverse(ostreetui.getColumnToBranchMap().begin(), ostreetui.getColumnToBranchMap().end()); return hbox({ vbox(std::move(treeElements)), @@ -421,7 +403,7 @@ ftxui::Element addTreeLine(const RenderTree& treeLineType, const std::unordered_map& branchColorMap) { using namespace ftxui; - std::string relevantBranch = commit.branch; + const std::string relevantBranch = commit.branch; // create an empty branch tree line Elements tree(usedBranches.size(), text(COMMIT_NONE)); @@ -431,7 +413,7 @@ ftxui::Element addTreeLine(const RenderTree& treeLineType, continue; } - if (treeLineType == RenderTree::TREE_LINE_IGNORE_BRANCH && branch.first != relevantBranch) { + if (treeLineType == RenderTree::TREE_LINE_TREE || (treeLineType == RenderTree::TREE_LINE_IGNORE_BRANCH && branch.first != relevantBranch)) { tree.at(branch.second) = (text(COMMIT_TREE) | color(branchColorMap.at(branch.first))); } else if (treeLineType == RenderTree::TREE_LINE_NODE) { if (branch.first == relevantBranch) { @@ -439,8 +421,6 @@ ftxui::Element addTreeLine(const RenderTree& treeLineType, } else { tree.at(branch.second) = (text(COMMIT_TREE) | color(branchColorMap.at(branch.first))); } - } else if (treeLineType == RenderTree::TREE_LINE_TREE) { - tree.at(branch.second) = (text(COMMIT_TREE) | color(branchColorMap.at(branch.first))); } } diff --git a/src/core/commit.hpp b/src/core/commit.hpp index 49c20e8..bbb2f6e 100644 --- a/src/core/commit.hpp +++ b/src/core/commit.hpp @@ -14,36 +14,31 @@ #include #include -#include #include #include "ftxui/component/component_base.hpp" +#include #include "../util/cpplibostree.hpp" -namespace CommitRender { - - constexpr size_t COMMIT_DETAIL_LEVEL {4}; // lines per commit +class OSTreeTUI; - constexpr std::string COMMIT_NODE {" ☐"}; - constexpr std::string COMMIT_TREE {" │"}; - constexpr std::string COMMIT_NONE {" "}; - constexpr std::string INDENT {" "}; - constexpr std::string GAP_TREE_COMMITS {" "}; - - enum RenderTree { +namespace CommitRender { + // UI characters + constexpr std::string COMMIT_NODE {" ☐"}; + constexpr std::string COMMIT_TREE {" │"}; + constexpr std::string COMMIT_NONE {" "}; + // window dimensions + constexpr int COMMIT_WINDOW_HEIGHT {4}; + constexpr int COMMIT_WINDOW_WIDTH {32}; + constexpr int PROMOTION_WINDOW_HEIGHT {COMMIT_WINDOW_HEIGHT + 11}; + constexpr int PROMOTION_WINDOW_WIDTH {COMMIT_WINDOW_WIDTH + 8}; + // render tree types + enum RenderTree : uint8_t { TREE_LINE_NODE, // ☐ | | TREE_LINE_TREE, // | | | TREE_LINE_IGNORE_BRANCH // | | }; - enum RenderLine { - EMPTY, // - BRANCH_HEAD, // foo/bar - COMMIT_HASH, // a0f33cd9 - COMMIT_DATE, // 2024-04-27 09:12:55 +00:00 - COMMIT_SUBJ // Some Subject - }; - /** * @brief Creates a window, containing a hash and some of its details. * The window has a pre-defined position and snaps back to it, @@ -54,34 +49,18 @@ namespace CommitRender { * * @return Component */ - ftxui::Component CommitComponent(int position, - int& scrollOffset, - bool& inPromotionSelection, - std::string& promotionHash, - std::string& promotionBranch, - std::unordered_map& visibleBranches, - std::vector& columnToBranchMap, - std::string commit, - cpplibostree::OSTreeRepo& ostreerepo, - bool& refresh); + ftxui::Component CommitComponent(int position, const std::string& commit, OSTreeTUI& ostreetui); /** * @brief create a Renderer for the commit section * - * @param repo OSTree repository - * @param visibleCommitMap List of visible commit hashes - * @param visibleBranches List of visible branches + * @param ostreetui OSTreeTUI containing OSTreeRepo and UI info * @param branchColorMap Map from branch to its display color * @param selectedCommit Commit that should be marked as selected * @return ftxui::Element */ - ftxui::Element commitRender(cpplibostree::OSTreeRepo& repo, - const std::vector& visibleCommitMap, - const std::unordered_map& visibleBranches, - std::vector& columnToBranchMap, - const std::unordered_map& branchColorMap, - int scrollOffset, - size_t selectedCommit = 0); + ftxui::Element commitRender(OSTreeTUI& ostreetui, const std::unordered_map& branchColorMap, size_t selectedCommit = 0); /** * @brief build a commit-tree line @@ -92,9 +71,10 @@ namespace CommitRender { * @param branchColorMap branch colors * @return ftxui::Element commit-tree line */ - ftxui::Element addTreeLine(const RenderTree& treeLineType, - const cpplibostree::Commit& commit, - const std::unordered_map& usedBranches, - const std::unordered_map& branchColorMap); + ftxui::Element addTreeLine( + const RenderTree& treeLineType, + const cpplibostree::Commit& commit, + const std::unordered_map& usedBranches, + const std::unordered_map& branchColorMap); } // namespace CommitRender diff --git a/src/core/footer.cpp b/src/core/footer.cpp index ebb006f..ed7ace0 100644 --- a/src/core/footer.cpp +++ b/src/core/footer.cpp @@ -15,3 +15,7 @@ ftxui::Element Footer::footerRender() { void Footer::resetContent() { content = DEFAULT_CONTENT; } + +void Footer::setContent(std::string content) { + this->content = content; +} diff --git a/src/core/footer.hpp b/src/core/footer.hpp index 289467d..ba978e0 100644 --- a/src/core/footer.hpp +++ b/src/core/footer.hpp @@ -6,10 +6,6 @@ class Footer { -public: - const std::string DEFAULT_CONTENT {" || Alt+Q / Esc : Quit || Alt+R : Refresh || Alt+C : Copy commit hash || "}; - std::string content {DEFAULT_CONTENT}; - public: Footer() = default; @@ -18,4 +14,12 @@ class Footer { /// create a Renderer for the footer section ftxui::Element footerRender(); + + // Setter + void setContent(std::string content); + +private: + const std::string DEFAULT_CONTENT {" || Alt+Q : Quit || Alt+R : Refresh || Alt+C : Copy commit hash || Alt+P : Promote Commit "}; + std::string content {DEFAULT_CONTENT}; + }; diff --git a/src/core/manager.cpp b/src/core/manager.cpp index 0a835b7..8427f39 100644 --- a/src/core/manager.cpp +++ b/src/core/manager.cpp @@ -10,9 +10,11 @@ #include "../util/cpplibostree.hpp" +#include "OSTreeTUI.hpp" + // Manager -Manager::Manager(const ftxui::Component& infoView, const ftxui::Component& filterView) { +Manager::Manager(OSTreeTUI& ostreetui, const ftxui::Component& infoView, const ftxui::Component& filterView) : ostreetui(ostreetui) { using namespace ftxui; tabSelection = Menu(&tab_entries, &tab_index, MenuOption::HorizontalAnimated()); @@ -25,7 +27,17 @@ Manager::Manager(const ftxui::Component& infoView, const ftxui::Component& filte managerRenderer = Container::Vertical({ tabSelection, - tabContent + tabContent, + Renderer([] {return vbox({filler()}) | flex;}), // push elements apart + Renderer([&] { + Elements branches; + for (size_t i{ostreetui.getColumnToBranchMap().size()}; i > 0; i--) { + std::string branch = ostreetui.getColumnToBranchMap().at(i - 1); + std::string line = "――☐――― " + branch; + branches.push_back(text(line) | color(ostreetui.getBranchColorMap().at(branch))); + } + return vbox(branches); + }) }); } diff --git a/src/core/manager.hpp b/src/core/manager.hpp index becfa22..b254494 100644 --- a/src/core/manager.hpp +++ b/src/core/manager.hpp @@ -5,6 +5,7 @@ | different modes should be supported (like a rebase mode | exchangeable with the commit info) |___________________________________________________________*/ +#pragma once #include #include @@ -13,9 +14,16 @@ #include "../util/cpplibostree.hpp" +class OSTreeTUI; + /// Interchangeable View class Manager { public: + Manager(OSTreeTUI& ostreetui, const ftxui::Component& infoView, const ftxui::Component& filterView); + +private: + OSTreeTUI& ostreetui; + int tab_index{0}; std::vector tab_entries = { @@ -25,12 +33,10 @@ class Manager { ftxui::Component tabSelection; ftxui::Component tabContent; +public: // because the combination of all interchangeable views is very simple, // we can (in contrast to the other ones) render this one immediately ftxui::Component managerRenderer; - -public: - Manager(const ftxui::Component& infoView, const ftxui::Component& filterView); }; class CommitInfoManager { diff --git a/src/main.cpp b/src/main.cpp index 0990bbd..29eb9be 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -56,5 +56,6 @@ int main(int argc, const char** argv) { std::vector startupBranches = getArgOptions(args, {"-r", "--refs"}); // OSTree TUI - return OSTreeTUI::main(repo, startupBranches); + OSTreeTUI ostreetui(repo, startupBranches); + return ostreetui.run(); } diff --git a/src/util/cpplibostree.cpp b/src/util/cpplibostree.cpp index 57389a3..e97c4f5 100644 --- a/src/util/cpplibostree.cpp +++ b/src/util/cpplibostree.cpp @@ -52,15 +52,15 @@ namespace cpplibostree { return repo; } - std::string OSTreeRepo::getRepoPath() { + const std::string& OSTreeRepo::getRepoPath() const { return repoPath; } - CommitList OSTreeRepo::getCommitList() { + const CommitList& OSTreeRepo::getCommitList() const { return commitList; } - std::vector OSTreeRepo::getBranches() { + const std::vector& OSTreeRepo::getBranches() const { return branches; } @@ -320,7 +320,7 @@ namespace cpplibostree { ? "" : " --keep-metadata"); for (auto str : addMetadataStrings) { - command += "--add-metadata-string=\"" + str + "\""; + command += " --add-metadata-string=\"" + str + "\""; } command += " --tree=ref=" + hash; diff --git a/src/util/cpplibostree.hpp b/src/util/cpplibostree.hpp index d70b3d0..ba1b3df 100644 --- a/src/util/cpplibostree.hpp +++ b/src/util/cpplibostree.hpp @@ -92,11 +92,11 @@ namespace cpplibostree { OstreeRepo* _c(); /// Getter - std::string getRepoPath(); + const std::string& getRepoPath() const; /// Getter - CommitList getCommitList(); + const CommitList& getCommitList() const; /// Getter - std::vector getBranches(); + const std::vector& getBranches() const; // Methods @@ -143,6 +143,7 @@ namespace cpplibostree { * * @param hash hash of the commit to promote * @param newRef branch to promote to + * @param addMetadataStrings list of metadata strings to add -> KEY=VALUE * @param newSubject new commit subject, it needed * @param keepMetadata should new commit keep metadata of old commit * @return true on success From 8f6edc20daf0da8279ff82096ba68d40796c9cc0 Mon Sep 17 00:00:00 2001 From: Timon Ensel Date: Mon, 11 Nov 2024 15:02:46 +0100 Subject: [PATCH 3/5] Improve commit window list * Add selected-commit highlight * Improve commit scrolling and selection * Make commit list focusable by keyboard * Fix keyboard navigation in promotion window * Fix promotion colors --- src/core/OSTreeTUI.cpp | 105 ++++++++++++++++++++++------------------- src/core/OSTreeTUI.hpp | 14 +++++- src/core/commit.cpp | 64 ++++++++++++++++++------- src/core/commit.hpp | 2 +- src/core/manager.cpp | 8 ++++ src/core/manager.hpp | 9 ++-- 6 files changed, 131 insertions(+), 71 deletions(-) diff --git a/src/core/OSTreeTUI.cpp b/src/core/OSTreeTUI.cpp index 2d905a9..89023f9 100644 --- a/src/core/OSTreeTUI.cpp +++ b/src/core/OSTreeTUI.cpp @@ -41,36 +41,26 @@ OSTreeTUI::OSTreeTUI (const std::string& repo, const std::vector st // - UI ELEMENTS ---------- ---------- - visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); // TODO This update shouldn't be made here... - - // COMMIT TREE - /* The commit-tree is currentrly under a heavy rebuild, see implementation To-Dos below. - * For a general list of To-Dos refer to https://github.com/AP-Sensing/ostree-tui/pull/21 - * - * TODO bug fixes: - * > keyboard functionality (especially in-app navigation) - */ - - // parse all commits + // COMMIT TREE refresh_commitComponents(); tree = Renderer([&] { - refresh_commitComponents(); - selectedCommit = std::min(selectedCommit, visibleCommitViewMap.size() - 1); - // TODO check for promotion & pass information if needed - if (inPromotionSelection && promotionBranch.size() != 0) { - std::unordered_map promotionBranchColorMap{}; - for (auto& [str,col] : branchColorMap) { - if (str == promotionBranch) { - promotionBranchColorMap.insert({str,col}); - } else { - promotionBranchColorMap.insert({str,Color::GrayDark}); - } + refresh_commitComponents(); + selectedCommit = std::min(selectedCommit, visibleCommitViewMap.size() - 1); + // TODO check for promotion & pass information if needed + if (inPromotionSelection && promotionBranch.size() != 0) { + std::unordered_map promotionBranchColorMap{}; + for (auto& [str,col] : branchColorMap) { + if (str == promotionBranch) { + promotionBranchColorMap.insert({str,col}); + } else { + promotionBranchColorMap.insert({str,Color::GrayDark}); } - return CommitRender::commitRender(*this, promotionBranchColorMap, selectedCommit); } - return CommitRender::commitRender(*this, branchColorMap, selectedCommit); - }); + return CommitRender::commitRender(*this, promotionBranchColorMap); + } + return CommitRender::commitRender(*this, branchColorMap); + }); commitListComponent = Container::Horizontal({ tree, @@ -79,28 +69,15 @@ OSTreeTUI::OSTreeTUI (const std::string& repo, const std::vector st // window specific shortcuts commitListComponent = CatchEvent(commitListComponent, [&](Event event) { - // scroll - if (event.is_mouse() && event.mouse().button == Mouse::WheelUp) { - if (scrollOffset < 0) { - ++scrollOffset; - } - selectedCommit = -scrollOffset / 4; - return true; - } - if (event.is_mouse() && event.mouse().button == Mouse::WheelDown) { - --scrollOffset; - selectedCommit = -scrollOffset / 4; - return true; - } // switch through commits - if (event == Event::ArrowUp) { - scrollOffset = std::min(0, scrollOffset + 4); - selectedCommit = -scrollOffset / 4; + if ((! inPromotionSelection && event == Event::ArrowUp) || (event.is_mouse() && event.mouse().button == Mouse::WheelUp)) { + selectedCommit = std::max(0, static_cast(selectedCommit) - 1); + adjustScrollToSelectedCommit(); return true; } - if (event == Event::ArrowDown) { - scrollOffset -= 4; - selectedCommit = -scrollOffset / 4; + if ((! inPromotionSelection && event == Event::ArrowDown) || (event.is_mouse() && event.mouse().button == Mouse::WheelDown)) { + selectedCommit = std::min(selectedCommit + 1, getVisibleCommitViewMap().size() - 1); + adjustScrollToSelectedCommit(); return true; } return false; @@ -130,14 +107,14 @@ OSTreeTUI::OSTreeTUI (const std::string& repo, const std::vector st // interchangeable view (composed) manager = std::unique_ptr(new Manager(*this, infoView, filterView)); - managerRenderer = manager->managerRenderer; + managerRenderer = manager->getManagerRenderer(); // FOOTER footerRenderer = Renderer([&] { return footer.footerRender(); }); - // build together all components + // BUILD MAIN CONTAINER container = Component(managerRenderer); container = ResizableSplitLeft(commitListComponent, container, &logSize); container = ResizableSplitBottom(footerRenderer, container, &footerSize); @@ -166,6 +143,11 @@ OSTreeTUI::OSTreeTUI (const std::string& repo, const std::vector st if (event == Event::AltQ) { screen.ExitLoopClosure()(); return true; + } + // make commit list focussable + if (event == Event::ArrowLeft && managerRenderer->Focused() && manager->getTabIndex() == 0) { + commitListComponent->TakeFocus(); + return true; } return false; }); @@ -237,7 +219,7 @@ bool OSTreeTUI::refresh_repository() { return true; } -bool OSTreeTUI::setPromotionMode(bool active, std::string hash) { +bool OSTreeTUI::setPromotionMode(bool active, std::string hash, bool setPromotionBranch) { // deactivate promotion mode if (!active) { inPromotionSelection = false; @@ -248,7 +230,9 @@ bool OSTreeTUI::setPromotionMode(bool active, std::string hash) { // set promotion mode if (!inPromotionSelection || hash != promotionHash) { inPromotionSelection = true; - promotionBranch = promotionBranch.empty() ? columnToBranchMap.at(0) : promotionBranch; + if (setPromotionBranch) { + promotionBranch = promotionBranch.empty() ? columnToBranchMap.at(0) : promotionBranch; + } promotionHash = hash; return true; } @@ -288,20 +272,45 @@ std::vector OSTreeTUI::parseVisibleCommitMap(cpplibostree::OSTreeRe return visibleCommitViewMap; } +void OSTreeTUI::adjustScrollToSelectedCommit() { + // try to scroll it to the middle + int windowHeight = screen.dimy() - 4; + int scollOffsetToFitCommitToTop = - selectedCommit * CommitRender::COMMIT_WINDOW_HEIGHT; + int newScroll = scollOffsetToFitCommitToTop + windowHeight / 2 - CommitRender::COMMIT_WINDOW_HEIGHT; + // adjust if on edges + int min = 0; + int max = - windowHeight - static_cast(visibleCommitViewMap.size() - 1) * CommitRender::COMMIT_WINDOW_HEIGHT; + scrollOffset = std::max(newScroll, max); + scrollOffset = std::min(min, newScroll); +} + // SETTER & non-const GETTER void OSTreeTUI::setPromotionBranch(std::string promotionBranch) { this->promotionBranch = promotionBranch; } +void OSTreeTUI::setSelectedCommit(size_t selectedCommit) { + this->selectedCommit = selectedCommit; + adjustScrollToSelectedCommit(); +} + std::vector& OSTreeTUI::getColumnToBranchMap() { return columnToBranchMap; } +ftxui::ScreenInteractive& OSTreeTUI::getScreen() { + return screen; +} + // GETTER const cpplibostree::OSTreeRepo& OSTreeTUI::getOstreeRepo() const { return ostreeRepo; } +const size_t& OSTreeTUI::getSelectedCommit() const { + return selectedCommit; +} + const std::string& OSTreeTUI::getPromotionBranch() const { return promotionBranch; } diff --git a/src/core/OSTreeTUI.hpp b/src/core/OSTreeTUI.hpp index b1b2187..0c431b7 100644 --- a/src/core/OSTreeTUI.hpp +++ b/src/core/OSTreeTUI.hpp @@ -52,7 +52,7 @@ class OSTreeTUI { * @param hash provide if setting mode to true * @return false, if other promotion gets overwritten */ - bool setPromotionMode(bool active, std::string hash = ""); + bool setPromotionMode(bool active, std::string hash = "", bool setPromotionBranch = true); /** @brief promote a commit * @param hash @@ -71,13 +71,23 @@ class OSTreeTUI { */ std::vector parseVisibleCommitMap(cpplibostree::OSTreeRepo& repo, std::unordered_map& visibleBranches); + /** + * @brief Adjust scroll offset to fit the selected commit + */ + void adjustScrollToSelectedCommit(); + public: - // "SETTER" & non-const GETTER + // SETTER void setPromotionBranch(std::string promotionBranch); + void setSelectedCommit(size_t selectedCommit); + + // non-const GETTER std::vector& getColumnToBranchMap(); + ftxui::ScreenInteractive& getScreen(); // GETTER const cpplibostree::OSTreeRepo& getOstreeRepo() const; + const size_t& getSelectedCommit() const; const std::string& getPromotionBranch() const; const std::unordered_map& getVisibleBranches() const; const std::vector& getColumnToBranchMap() const; diff --git a/src/core/commit.cpp b/src/core/commit.cpp index b9fadcd..f09a738 100644 --- a/src/core/commit.cpp +++ b/src/core/commit.cpp @@ -49,11 +49,17 @@ Decorator PositionAndSize(int left, int top, int width, int height) { } /// Partially inspired from https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp -Element DefaultRenderState(const WindowRenderState& state) { +Element DefaultRenderState(const WindowRenderState& state, ftxui::Color selectedColor = Color::White, bool dimmable = true) { Element element = state.inner; - //if (!state.active) { - // element |= dim; - //} + if (! dimmable) { + selectedColor = Color::White; + } + if (selectedColor == Color::White & dimmable) { + element |= dim; + } else { + element |= bold; + } + element |= color(selectedColor); element = window(text(state.title), element); element |= clear_under; @@ -66,7 +72,8 @@ Element DefaultRenderState(const WindowRenderState& state) { class CommitComponentImpl : public ComponentBase, public WindowOptions { public: explicit CommitComponentImpl(int position, std::string commit, OSTreeTUI& ostreetui) : - hash(std::move(commit)), + commitPosition(position), + hash(std::move(commit)), ostreetui(ostreetui), commit(ostreetui.getOstreeRepo().getCommitList().at(hash)), newVersion(this->commit.version), @@ -90,9 +97,11 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { } private: - void resetWindow() { - left() = drag_initial_x; - top() = drag_initial_y; + void resetWindow(bool positionReset = true) { + if (positionReset) { + left() = drag_initial_x; + top() = drag_initial_y; + } width() = width_initial; height() = height_initial; // reset window contents @@ -108,7 +117,16 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { simpleCommit = inner; DetachAllChildren(); Add(promotionView); - TakeFocus(); + if (! Focused()) { + TakeFocus(); + // manually fix focus issues + // change to proper control layout if possible... + ostreetui.getScreen().Post(Event::ArrowDown); + ostreetui.getScreen().Post(Event::ArrowRight); + ostreetui.getScreen().Post(Event::ArrowDown); + ostreetui.getScreen().Post(Event::ArrowRight); + ostreetui.getScreen().Post(Event::ArrowUp); + } } void executePromotion() { @@ -129,7 +147,11 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { Element Render() final { // check if promotion was started not from drag & drop, but from ostreetui if (ostreetui.getInPromotionSelection() && ostreetui.getPromotionHash() == hash) { - startPromotionWindow(); + if (! ostreetui.getPromotionBranch().empty()) { + startPromotionWindow(); + } else { + resetWindow(false); + } } auto element = ComponentBase::Render(); @@ -141,7 +163,11 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { drag_ }; - element = render ? render(state) : DefaultRenderState(state); + if (commitPosition == ostreetui.getSelectedCommit()) { // selected & not in promotion + element = render ? render(state) : DefaultRenderState(state, ostreetui.getBranchColorMap().at(commit.branch), ostreetui.getPromotionHash() != hash); + } else { + element = render ? render(state) : DefaultRenderState(state, Color::White, ostreetui.getPromotionHash() != hash); + } // Position and record the drawn area of the window. element |= reflect(box_window_); @@ -188,6 +214,11 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { return true; } + if (event.mouse().button == Mouse::Left) { + // update ostreetui + ostreetui.setSelectedCommit(commitPosition); + } + mouse_hover_ = box_window_.Contain(event.mouse().x, event.mouse().y); // potentially indicate mouse hover //if (box_window_.Contain(event.mouse().x, event.mouse().y)) {} @@ -209,8 +240,8 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { if (drag_) { left() = event.mouse().x - drag_start_x - box_.x_min; top() = event.mouse().y - drag_start_y - box_.y_min; - // promotion - ostreetui.setPromotionMode(true, hash); // TODO switch to ostreetui call + // potential promotion + ostreetui.setPromotionMode(true, hash, false); // calculate which branch currently is hovered over ostreetui.setPromotionBranch(""); const int branch_pos = event.mouse().x / 2; @@ -226,7 +257,7 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { } } else { // not promotion - ostreetui.setPromotionMode(false, hash); + ostreetui.setPromotionMode(false); } // Clamp the window size. @@ -287,6 +318,7 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { bool drag_ = false; // ostree-tui specific members + int commitPosition; std::string hash; OSTreeTUI& ostreetui; @@ -303,7 +335,7 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { text(""), text(" Promote Commit...") | bold, text(""), - text(" ☐ todocommithash") | bold, + text(" ☐ " + hash.substr(0, 8)) | bold, }); }), Container::Horizontal({ @@ -346,7 +378,7 @@ ftxui::Component CommitComponent(int position, const std::string& commit, OSTree return ftxui::Make(position, commit, ostreetui); } -ftxui::Element commitRender(OSTreeTUI& ostreetui, const std::unordered_map& branchColorMap, size_t selectedCommit) { +ftxui::Element commitRender(OSTreeTUI& ostreetui, const std::unordered_map& branchColorMap) { using namespace ftxui; int scrollOffset = ostreetui.getScrollOffset(); diff --git a/src/core/commit.hpp b/src/core/commit.hpp index bbb2f6e..d455132 100644 --- a/src/core/commit.hpp +++ b/src/core/commit.hpp @@ -60,7 +60,7 @@ namespace CommitRender { * @return ftxui::Element */ ftxui::Element commitRender(OSTreeTUI& ostreetui, const std::unordered_map& branchColorMap, size_t selectedCommit = 0); + ftxui::Color>& branchColorMap); /** * @brief build a commit-tree line diff --git a/src/core/manager.cpp b/src/core/manager.cpp index 8427f39..f49725b 100644 --- a/src/core/manager.cpp +++ b/src/core/manager.cpp @@ -41,6 +41,14 @@ Manager::Manager(OSTreeTUI& ostreetui, const ftxui::Component& infoView, const f }); } +ftxui::Component Manager::getManagerRenderer() { + return managerRenderer; +} + +const int& Manager::getTabIndex() const { + return tab_index; +} + // BranchBoxManager BranchBoxManager::BranchBoxManager(cpplibostree::OSTreeRepo& repo, std::unordered_map& visibleBranches) { diff --git a/src/core/manager.hpp b/src/core/manager.hpp index b254494..5e41dba 100644 --- a/src/core/manager.hpp +++ b/src/core/manager.hpp @@ -25,18 +25,19 @@ class Manager { OSTreeTUI& ostreetui; int tab_index{0}; - std::vector tab_entries = { " Info ", " Filter " }; + // because the combination of all interchangeable views is very simple, + // we can (in contrast to the other ones) render this one here + ftxui::Component managerRenderer; ftxui::Component tabSelection; ftxui::Component tabContent; public: - // because the combination of all interchangeable views is very simple, - // we can (in contrast to the other ones) render this one immediately - ftxui::Component managerRenderer; + ftxui::Component getManagerRenderer(); + const int& getTabIndex() const; }; class CommitInfoManager { From ac583df405724c3a2cccd74350681132766f3209 Mon Sep 17 00:00:00 2001 From: Timon Ensel Date: Tue, 12 Nov 2024 08:37:24 +0100 Subject: [PATCH 4/5] run clang format --- .clang-format | 4 +- scripts/check_clang_format.sh | 0 scripts/run_clang_format.sh | 0 src/core/OSTreeTUI.cpp | 621 +++++++++++++------------ src/core/OSTreeTUI.hpp | 78 ++-- src/core/commit.cpp | 832 +++++++++++++++++----------------- src/core/commit.hpp | 103 +++-- src/core/footer.cpp | 19 +- src/core/footer.hpp | 14 +- src/core/manager.cpp | 161 +++---- src/core/manager.hpp | 33 +- src/main.cpp | 77 ++-- src/util/cpplibostree.cpp | 561 ++++++++++++----------- src/util/cpplibostree.hpp | 322 ++++++------- 14 files changed, 1417 insertions(+), 1408 deletions(-) mode change 100644 => 100755 scripts/check_clang_format.sh mode change 100644 => 100755 scripts/run_clang_format.sh diff --git a/.clang-format b/.clang-format index a587cbb..0b98b25 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,6 @@ # Defines the Chromium style for automatic reformatting. # http://clang.llvm.org/docs/ClangFormatStyleOptions.html BasedOnStyle: Chromium -Standard: Cpp20 +Standard: c++20 +IndentWidth: 4 +ColumnLimit: 100 diff --git a/scripts/check_clang_format.sh b/scripts/check_clang_format.sh old mode 100644 new mode 100755 diff --git a/scripts/run_clang_format.sh b/scripts/run_clang_format.sh old mode 100644 new mode 100755 diff --git a/src/core/OSTreeTUI.cpp b/src/core/OSTreeTUI.cpp index 89023f9..1b5c5ea 100644 --- a/src/core/OSTreeTUI.cpp +++ b/src/core/OSTreeTUI.cpp @@ -1,407 +1,404 @@ #include "OSTreeTUI.hpp" +#include #include #include #include #include +#include #include -#include #include -#include -#include +#include +#include // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp #include "ftxui/component/component.hpp" // for Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop #include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive -#include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border -#include // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp +#include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border #include "clip.h" #include "../util/cpplibostree.hpp" -OSTreeTUI::OSTreeTUI (const std::string& repo, const std::vector startupBranches): - ostreeRepo(repo), selectedCommit(0), screen(ftxui::ScreenInteractive::Fullscreen()) -{ +OSTreeTUI::OSTreeTUI(const std::string& repo, const std::vector startupBranches) + : ostreeRepo(repo), selectedCommit(0), screen(ftxui::ScreenInteractive::Fullscreen()) { using namespace ftxui; - // set all branches as visible and define a branch color - for (const auto& branch : ostreeRepo.getBranches()) { - // if startupBranches are defined, set all as non-visible - visibleBranches[branch] = startupBranches.size() == 0 ? true : false; - std::hash nameHash{}; - branchColorMap[branch] = Color::Palette256((nameHash(branch) + 10) % 256); - } - // if startupBranches are defined, only set those visible - if (startupBranches.size() != 0) { - for (const auto& branch : startupBranches) { - visibleBranches[branch] = true; - } - } - - // - UI ELEMENTS ---------- ---------- - - // COMMIT TREE - refresh_commitComponents(); - - tree = Renderer([&] { - refresh_commitComponents(); - selectedCommit = std::min(selectedCommit, visibleCommitViewMap.size() - 1); - // TODO check for promotion & pass information if needed - if (inPromotionSelection && promotionBranch.size() != 0) { - std::unordered_map promotionBranchColorMap{}; - for (auto& [str,col] : branchColorMap) { - if (str == promotionBranch) { - promotionBranchColorMap.insert({str,col}); - } else { - promotionBranchColorMap.insert({str,Color::GrayDark}); - } - } - return CommitRender::commitRender(*this, promotionBranchColorMap); - } - return CommitRender::commitRender(*this, branchColorMap); - }); - - commitListComponent = Container::Horizontal({ - tree, - commitList - }); - - // window specific shortcuts - commitListComponent = CatchEvent(commitListComponent, [&](Event event) { - // switch through commits - if ((! inPromotionSelection && event == Event::ArrowUp) || (event.is_mouse() && event.mouse().button == Mouse::WheelUp)) { - selectedCommit = std::max(0, static_cast(selectedCommit) - 1); - adjustScrollToSelectedCommit(); - return true; - } - if ((! inPromotionSelection && event == Event::ArrowDown) || (event.is_mouse() && event.mouse().button == Mouse::WheelDown)) { - selectedCommit = std::min(selectedCommit + 1, getVisibleCommitViewMap().size() - 1); - adjustScrollToSelectedCommit(); - return true; - } - return false; - }); - - // INTERCHANGEABLE VIEW - // info - infoView = Renderer([&] { - visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); // TODO This update shouldn't be made here... - if (visibleCommitViewMap.size() <= 0) { - return text(" no commit info available ") | color(Color::RedLight) | bold | center; - } - return CommitInfoManager::renderInfoView(ostreeRepo.getCommitList().at(visibleCommitViewMap.at(selectedCommit))); + // set all branches as visible and define a branch color + for (const auto& branch : ostreeRepo.getBranches()) { + // if startupBranches are defined, set all as non-visible + visibleBranches[branch] = startupBranches.size() == 0 ? true : false; + std::hash nameHash{}; + branchColorMap[branch] = Color::Palette256((nameHash(branch) + 10) % 256); + } + // if startupBranches are defined, only set those visible + if (startupBranches.size() != 0) { + for (const auto& branch : startupBranches) { + visibleBranches[branch] = true; + } + } + + // - UI ELEMENTS ---------- ---------- + + // COMMIT TREE + refresh_commitComponents(); + + tree = Renderer([&] { + refresh_commitComponents(); + selectedCommit = std::min(selectedCommit, visibleCommitViewMap.size() - 1); + // TODO check for promotion & pass information if needed + if (inPromotionSelection && promotionBranch.size() != 0) { + std::unordered_map promotionBranchColorMap{}; + for (auto& [str, col] : branchColorMap) { + if (str == promotionBranch) { + promotionBranchColorMap.insert({str, col}); + } else { + promotionBranchColorMap.insert({str, Color::GrayDark}); + } + } + return CommitRender::commitRender(*this, promotionBranchColorMap); + } + return CommitRender::commitRender(*this, branchColorMap); + }); + + commitListComponent = Container::Horizontal({tree, commitList}); + + // window specific shortcuts + commitListComponent = CatchEvent(commitListComponent, [&](Event event) { + // switch through commits + if ((!inPromotionSelection && event == Event::ArrowUp) || + (event.is_mouse() && event.mouse().button == Mouse::WheelUp)) { + selectedCommit = std::max(0, static_cast(selectedCommit) - 1); + adjustScrollToSelectedCommit(); + return true; + } + if ((!inPromotionSelection && event == Event::ArrowDown) || + (event.is_mouse() && event.mouse().button == Mouse::WheelDown)) { + selectedCommit = std::min(selectedCommit + 1, getVisibleCommitViewMap().size() - 1); + adjustScrollToSelectedCommit(); + return true; + } + return false; + }); + + // INTERCHANGEABLE VIEW + // info + infoView = Renderer([&] { + visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); + if (visibleCommitViewMap.size() <= 0) { + return text(" no commit info available ") | color(Color::RedLight) | bold | center; + } + return CommitInfoManager::renderInfoView( + ostreeRepo.getCommitList().at(visibleCommitViewMap.at(selectedCommit))); }); - // filter - filterManager = std::unique_ptr(new BranchBoxManager(ostreeRepo, visibleBranches)); - filterView = Renderer(filterManager->branchBoxes, [&] { - return filterManager->branchBoxRender(); - }); - filterView = CatchEvent(filterView, [&](Event event) { - if (event.is_mouse() && event.mouse().button == Mouse::Button::Left) { - refresh_commitListComoponent(); - } - return false; - }); - - // interchangeable view (composed) - manager = std::unique_ptr(new Manager(*this, infoView, filterView)); - managerRenderer = manager->getManagerRenderer(); - - // FOOTER - footerRenderer = Renderer([&] { - return footer.footerRender(); - }); - - // BUILD MAIN CONTAINER - container = Component(managerRenderer); - container = ResizableSplitLeft(commitListComponent, container, &logSize); - container = ResizableSplitBottom(footerRenderer, container, &footerSize); - - commitListComponent->TakeFocus(); - - // add application shortcuts - mainContainer = CatchEvent(container | border, [&](const Event& event) { - if (event == Event::AltP) { - setPromotionMode(true, visibleCommitViewMap.at(selectedCommit)); - } - // copy commit id - if (event == Event::AltC) { - std::string hash = visibleCommitViewMap.at(selectedCommit); - clip::set_text(hash); - notificationText = " Copied Hash " + hash + " "; - return true; - } - // refresh repository - if (event == Event::AltR) { - refresh_repository(); - notificationText = " Refreshed Repository Data "; - return true; - } - // exit - if (event == Event::AltQ) { - screen.ExitLoopClosure()(); - return true; - } - // make commit list focussable - if (event == Event::ArrowLeft && managerRenderer->Focused() && manager->getTabIndex() == 0) { - commitListComponent->TakeFocus(); - return true; - } - return false; - }); + // filter + filterManager = + std::unique_ptr(new BranchBoxManager(ostreeRepo, visibleBranches)); + filterView = + Renderer(filterManager->branchBoxes, [&] { return filterManager->branchBoxRender(); }); + filterView = CatchEvent(filterView, [&](Event event) { + if (event.is_mouse() && event.mouse().button == Mouse::Button::Left) { + refresh_commitListComoponent(); + } + return false; + }); + + // interchangeable view (composed) + manager = std::unique_ptr(new Manager(*this, infoView, filterView)); + managerRenderer = manager->getManagerRenderer(); + + // FOOTER + footerRenderer = Renderer([&] { return footer.footerRender(); }); + + // BUILD MAIN CONTAINER + container = Component(managerRenderer); + container = ResizableSplitLeft(commitListComponent, container, &logSize); + container = ResizableSplitBottom(footerRenderer, container, &footerSize); + + commitListComponent->TakeFocus(); + + // add application shortcuts + mainContainer = CatchEvent(container | border, [&](const Event& event) { + if (event == Event::AltP) { + setPromotionMode(true, visibleCommitViewMap.at(selectedCommit)); + } + // copy commit id + if (event == Event::AltC) { + std::string hash = visibleCommitViewMap.at(selectedCommit); + clip::set_text(hash); + notificationText = " Copied Hash " + hash + " "; + return true; + } + // refresh repository + if (event == Event::AltR) { + refresh_repository(); + notificationText = " Refreshed Repository Data "; + return true; + } + // exit + if (event == Event::AltQ) { + screen.ExitLoopClosure()(); + return true; + } + // make commit list focussable + if (event == Event::ArrowLeft && managerRenderer->Focused() && + manager->getTabIndex() == 0) { + commitListComponent->TakeFocus(); + return true; + } + return false; + }); } int OSTreeTUI::run() { - using namespace ftxui; - // footer notification update loader - // Probably not the best solution, having an active wait and should maybe - // only be started, once a notification is set... - bool runSubThreads{true}; - std::thread footerNotificationUpdater([&] { - while (runSubThreads) { - using namespace std::chrono_literals; - // notification is set - if (notificationText != "") { - footer.setContent(notificationText); - screen.Post(Event::Custom); - std::this_thread::sleep_for(2s); - // clear notification - notificationText = ""; - footer.resetContent(); - screen.Post(Event::Custom); - } - std::this_thread::sleep_for(0.2s); - } - }); - - screen.Loop(mainContainer); - runSubThreads = false; - footerNotificationUpdater.join(); - - return EXIT_SUCCESS; + using namespace ftxui; + // footer notification update loader + // Probably not the best solution, having an active wait and should maybe + // only be started, once a notification is set... + bool runSubThreads{true}; + std::thread footerNotificationUpdater([&] { + while (runSubThreads) { + using namespace std::chrono_literals; + // notification is set + if (notificationText != "") { + footer.setContent(notificationText); + screen.Post(Event::Custom); + std::this_thread::sleep_for(2s); + // clear notification + notificationText = ""; + footer.resetContent(); + screen.Post(Event::Custom); + } + std::this_thread::sleep_for(0.2s); + } + }); + + screen.Loop(mainContainer); + runSubThreads = false; + footerNotificationUpdater.join(); + + return EXIT_SUCCESS; } void OSTreeTUI::refresh_commitComponents() { - using namespace ftxui; - - commitComponents.clear(); - int i{0}; - visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); - for (auto& hash : visibleCommitViewMap) { - commitComponents.push_back( - CommitRender::CommitComponent(i, hash, *this) - ); - i++; - } - - commitList = commitComponents.size() == 0 - ? Renderer([&] { return text(" no commits to be shown ") | color(Color::Red); }) - : Container::Stacked(commitComponents); + using namespace ftxui; + + commitComponents.clear(); + int i{0}; + visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); + for (auto& hash : visibleCommitViewMap) { + commitComponents.push_back(CommitRender::CommitComponent(i, hash, *this)); + i++; + } + + commitList = + commitComponents.size() == 0 + ? Renderer([&] { return text(" no commits to be shown ") | color(Color::Red); }) + : Container::Stacked(commitComponents); } void OSTreeTUI::refresh_commitListComoponent() { - using namespace ftxui; - - commitListComponent->DetachAllChildren(); - refresh_commitComponents(); - Component tmp = Container::Horizontal({ - tree, - commitList - }); - commitListComponent->Add(tmp); + using namespace ftxui; + + commitListComponent->DetachAllChildren(); + refresh_commitComponents(); + Component tmp = Container::Horizontal({tree, commitList}); + commitListComponent->Add(tmp); } bool OSTreeTUI::refresh_repository() { - ostreeRepo.updateData(); - refresh_commitListComoponent(); - return true; + ostreeRepo.updateData(); + refresh_commitListComoponent(); + return true; } bool OSTreeTUI::setPromotionMode(bool active, std::string hash, bool setPromotionBranch) { - // deactivate promotion mode - if (!active) { - inPromotionSelection = false; - promotionBranch = ""; - promotionHash = ""; - return true; - } - // set promotion mode - if (!inPromotionSelection || hash != promotionHash) { - inPromotionSelection = true; - if (setPromotionBranch) { - promotionBranch = promotionBranch.empty() ? columnToBranchMap.at(0) : promotionBranch; - } - promotionHash = hash; - return true; - } - // nothing to update - return false; + // deactivate promotion mode + if (!active) { + inPromotionSelection = false; + promotionBranch = ""; + promotionHash = ""; + return true; + } + // set promotion mode + if (!inPromotionSelection || hash != promotionHash) { + inPromotionSelection = true; + if (setPromotionBranch) { + promotionBranch = promotionBranch.empty() ? columnToBranchMap.at(0) : promotionBranch; + } + promotionHash = hash; + return true; + } + // nothing to update + return false; } -bool OSTreeTUI::promoteCommit(std::string hash, std::string branch, std::vector metadataStrings, std::string newSubject, bool keepMetadata) { - bool success = ostreeRepo.promoteCommit(hash, branch, metadataStrings, newSubject, keepMetadata); - setPromotionMode(false); - // reload repository - if (success) { - scrollOffset = 0; - selectedCommit = 0; - screen.PostEvent(ftxui::Event::AltR); - } - return success; +bool OSTreeTUI::promoteCommit(std::string hash, + std::string branch, + std::vector metadataStrings, + std::string newSubject, + bool keepMetadata) { + bool success = + ostreeRepo.promoteCommit(hash, branch, metadataStrings, newSubject, keepMetadata); + setPromotionMode(false); + // reload repository + if (success) { + scrollOffset = 0; + selectedCommit = 0; + screen.PostEvent(ftxui::Event::AltR); + } + return success; } -std::vector OSTreeTUI::parseVisibleCommitMap(cpplibostree::OSTreeRepo& repo, - std::unordered_map& visibleBranches) { - - std::vector visibleCommitViewMap{}; - // get filtered commits - visibleCommitViewMap = {}; - for (const auto& commitPair : repo.getCommitList()) { - if (visibleBranches[commitPair.second.branch]) { - visibleCommitViewMap.push_back(commitPair.first); - } - } - // sort by date - std::sort(visibleCommitViewMap.begin(), visibleCommitViewMap.end(), [&](const std::string& a, const std::string& b) { - return repo.getCommitList().at(a).timestamp - > repo.getCommitList().at(b).timestamp; - }); - - return visibleCommitViewMap; +std::vector OSTreeTUI::parseVisibleCommitMap( + cpplibostree::OSTreeRepo& repo, + std::unordered_map& visibleBranches) { + std::vector visibleCommitViewMap{}; + // get filtered commits + visibleCommitViewMap = {}; + for (const auto& commitPair : repo.getCommitList()) { + if (visibleBranches[commitPair.second.branch]) { + visibleCommitViewMap.push_back(commitPair.first); + } + } + // sort by date + std::sort(visibleCommitViewMap.begin(), visibleCommitViewMap.end(), + [&](const std::string& a, const std::string& b) { + return repo.getCommitList().at(a).timestamp > + repo.getCommitList().at(b).timestamp; + }); + + return visibleCommitViewMap; } void OSTreeTUI::adjustScrollToSelectedCommit() { - // try to scroll it to the middle - int windowHeight = screen.dimy() - 4; - int scollOffsetToFitCommitToTop = - selectedCommit * CommitRender::COMMIT_WINDOW_HEIGHT; - int newScroll = scollOffsetToFitCommitToTop + windowHeight / 2 - CommitRender::COMMIT_WINDOW_HEIGHT; - // adjust if on edges - int min = 0; - int max = - windowHeight - static_cast(visibleCommitViewMap.size() - 1) * CommitRender::COMMIT_WINDOW_HEIGHT; - scrollOffset = std::max(newScroll, max); - scrollOffset = std::min(min, newScroll); + // try to scroll it to the middle + int windowHeight = screen.dimy() - 4; + int scollOffsetToFitCommitToTop = -selectedCommit * CommitRender::COMMIT_WINDOW_HEIGHT; + int newScroll = + scollOffsetToFitCommitToTop + windowHeight / 2 - CommitRender::COMMIT_WINDOW_HEIGHT; + // adjust if on edges + int min = 0; + int max = -windowHeight - static_cast(visibleCommitViewMap.size() - 1) * + CommitRender::COMMIT_WINDOW_HEIGHT; + scrollOffset = std::max(newScroll, max); + scrollOffset = std::min(min, newScroll); } // SETTER & non-const GETTER void OSTreeTUI::setPromotionBranch(std::string promotionBranch) { - this->promotionBranch = promotionBranch; + this->promotionBranch = promotionBranch; } void OSTreeTUI::setSelectedCommit(size_t selectedCommit) { - this->selectedCommit = selectedCommit; - adjustScrollToSelectedCommit(); + this->selectedCommit = selectedCommit; + adjustScrollToSelectedCommit(); } std::vector& OSTreeTUI::getColumnToBranchMap() { - return columnToBranchMap; + return columnToBranchMap; } ftxui::ScreenInteractive& OSTreeTUI::getScreen() { - return screen; + return screen; } // GETTER const cpplibostree::OSTreeRepo& OSTreeTUI::getOstreeRepo() const { - return ostreeRepo; + return ostreeRepo; } const size_t& OSTreeTUI::getSelectedCommit() const { - return selectedCommit; + return selectedCommit; } const std::string& OSTreeTUI::getPromotionBranch() const { - return promotionBranch; + return promotionBranch; } const std::unordered_map& OSTreeTUI::getVisibleBranches() const { - return visibleBranches; + return visibleBranches; } const std::vector& OSTreeTUI::getColumnToBranchMap() const { - return columnToBranchMap; + return columnToBranchMap; } const std::vector& OSTreeTUI::getVisibleCommitViewMap() const { - return visibleCommitViewMap; + return visibleCommitViewMap; } const std::unordered_map& OSTreeTUI::getBranchColorMap() const { - return branchColorMap; + return branchColorMap; } const int& OSTreeTUI::getScrollOffset() const { - return scrollOffset; + return scrollOffset; } const bool& OSTreeTUI::getInPromotionSelection() const { - return inPromotionSelection; + return inPromotionSelection; } const std::string& OSTreeTUI::getPromotionHash() const { - return promotionHash; + return promotionHash; } // STATIC int OSTreeTUI::showHelp(const std::string& caller, const std::string& errorMessage) { - using namespace ftxui; - - // define command line options - std::vector> command_options{ - // option, arguments, meaning - {"-h, --help", "", "Show help options. The REPOSITORY_PATH can be omitted"}, - {"-r, --refs", "REF [REF...]", "Specify a list of visible refs at startup if not specified, show all refs"}, - }; - - Elements options {text("Options:")}; - Elements arguments {text("Arguments:")}; - Elements meanings {text("Meaning:")}; - for (const auto& command : command_options) { - options.push_back(text(command.at(0) + " ") | color(Color::GrayLight)); - arguments.push_back(text(command.at(1) + " ") | color(Color::GrayLight)); - meanings.push_back(text(command.at(2) + " ")); - } - - auto helpPage = vbox({ - errorMessage.empty() ? filler() : (text(errorMessage) | bold | color(Color::Red) | flex), - hbox({ - text("Usage: "), - text(caller) | color(Color::GrayLight), - text(" REPOSITORY_PATH") | color(Color::Yellow), - text(" [OPTION...]") | color(Color::Yellow) - }), - text(""), - hbox({ - vbox(options), - vbox(arguments), - vbox(meanings), - }), - text(""), - hbox({ - text("Report bugs at "), - text("Github.com/AP-Sensing/ostree-tui") | hyperlink("https://github.com/AP-Sensing/ostree-tui") - }), - text("") - }); - - auto screen = Screen::Create(Dimension::Fit(helpPage)); - Render(screen, helpPage); - screen.Print(); - std::cout << "\n"; - - return errorMessage.empty(); + using namespace ftxui; + + // define command line options + std::vector> command_options{ + // option, arguments, meaning + {"-h, --help", "", "Show help options. The REPOSITORY_PATH can be omitted"}, + {"-r, --refs", "REF [REF...]", + "Specify a list of visible refs at startup if not specified, show all refs"}, + }; + + Elements options{text("Options:")}; + Elements arguments{text("Arguments:")}; + Elements meanings{text("Meaning:")}; + for (const auto& command : command_options) { + options.push_back(text(command.at(0) + " ") | color(Color::GrayLight)); + arguments.push_back(text(command.at(1) + " ") | color(Color::GrayLight)); + meanings.push_back(text(command.at(2) + " ")); + } + + auto helpPage = vbox( + {errorMessage.empty() ? filler() : (text(errorMessage) | bold | color(Color::Red) | flex), + hbox({text("Usage: "), text(caller) | color(Color::GrayLight), + text(" REPOSITORY_PATH") | color(Color::Yellow), + text(" [OPTION...]") | color(Color::Yellow)}), + text(""), + hbox({ + vbox(options), + vbox(arguments), + vbox(meanings), + }), + text(""), + hbox({text("Report bugs at "), text("Github.com/AP-Sensing/ostree-tui") | + hyperlink("https://github.com/AP-Sensing/ostree-tui")}), + text("")}); + + auto screen = Screen::Create(Dimension::Fit(helpPage)); + Render(screen, helpPage); + screen.Print(); + std::cout << "\n"; + + return errorMessage.empty(); } int OSTreeTUI::showVersion() { - using namespace ftxui; - - auto versionText = text("ostree-tui 0.2.1"); - - auto screen = Screen::Create(Dimension::Fit(versionText)); - Render(screen, versionText); - screen.Print(); - std::cout << "\n"; - - return 0; + using namespace ftxui; + + auto versionText = text("ostree-tui 0.2.1"); + + auto screen = Screen::Create(Dimension::Fit(versionText)); + Render(screen, versionText); + screen.Print(); + std::cout << "\n"; + + return 0; } diff --git a/src/core/OSTreeTUI.hpp b/src/core/OSTreeTUI.hpp index 0c431b7..a673f34 100644 --- a/src/core/OSTreeTUI.hpp +++ b/src/core/OSTreeTUI.hpp @@ -12,7 +12,7 @@ #include "ftxui/component/component.hpp" // for Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop #include "ftxui/component/component_base.hpp" // for ComponentBase #include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive -#include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border +#include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border #include "commit.hpp" #include "footer.hpp" @@ -21,20 +21,22 @@ #include "../util/cpplibostree.hpp" class OSTreeTUI { -public: + public: /** * @brief Constructs, builds and assembles all components of the OSTreeTUI. - * + * * @param repo ostree repository (OSTreeRepo) - * @param startupBranches optional list of branches to pre-select at startup (providing nothing will display all branches) - */ - explicit OSTreeTUI (const std::string& repo, const std::vector startupBranches = {}); - + * @param startupBranches optional list of branches to pre-select at startup (providing nothing + * will display all branches) + */ + explicit OSTreeTUI(const std::string& repo, + const std::vector startupBranches = {}); + /** * @brief Runs the OSTreeTUI (starts the ftxui screen loop). - * + * * @return exit code - */ + */ int run(); /// @brief Refresh Level 3: Refreshes the commit components @@ -55,28 +57,34 @@ class OSTreeTUI { bool setPromotionMode(bool active, std::string hash = "", bool setPromotionBranch = true); /** @brief promote a commit - * @param hash - * @param branch + * @param hash + * @param branch * @return promotion success - */ - bool promoteCommit(std::string hash, std::string branch, std::vector metadataStrings = {}, std::string newSubject = "", bool keepMetadata = true); + */ + bool promoteCommit(std::string hash, + std::string branch, + std::vector metadataStrings = {}, + std::string newSubject = "", + bool keepMetadata = true); -private: + private: /** * @brief Calculates all visible commits from an OSTreeRepo and a list of branches. - * + * * @param repo OSTreeRepo * @param visibleBranches Map: branch name -> visible - * @return Complete list of commit hashes in repo, that are part of the given branches + * @return Complete list of commit hashes in repo, that are part of the given branches */ - std::vector parseVisibleCommitMap(cpplibostree::OSTreeRepo& repo, std::unordered_map& visibleBranches); + std::vector parseVisibleCommitMap( + cpplibostree::OSTreeRepo& repo, + std::unordered_map& visibleBranches); /** * @brief Adjust scroll offset to fit the selected commit */ void adjustScrollToSelectedCommit(); -public: + public: // SETTER void setPromotionBranch(std::string promotionBranch); void setSelectedCommit(size_t selectedCommit); @@ -84,7 +92,7 @@ class OSTreeTUI { // non-const GETTER std::vector& getColumnToBranchMap(); ftxui::ScreenInteractive& getScreen(); - + // GETTER const cpplibostree::OSTreeRepo& getOstreeRepo() const; const size_t& getSelectedCommit() const; @@ -97,27 +105,29 @@ class OSTreeTUI { const bool& getInPromotionSelection() const; const std::string& getPromotionHash() const; -private: + private: // model cpplibostree::OSTreeRepo ostreeRepo; // backend states size_t selectedCommit; - std::unordered_map visibleBranches; // map branch -> visibe - std::vector columnToBranchMap; // map branch -> column in commit-tree (may be merged into one data-structure with visibleBranches) - std::vector visibleCommitViewMap; // map view-index -> commit-hash - std::unordered_map branchColorMap; // map branch -> color - std::string notificationText; // footer notification - + std::unordered_map visibleBranches; // map branch -> visibe + std::vector + columnToBranchMap; // map branch -> column in commit-tree (may be merged into one + // data-structure with visibleBranches) + std::vector visibleCommitViewMap; // map view-index -> commit-hash + std::unordered_map branchColorMap; // map branch -> color + std::string notificationText; // footer notification + // view states int scrollOffset{0}; bool inPromotionSelection{false}; - std::string promotionHash; - std::string promotionBranch; + std::string promotionHash; + std::string promotionBranch; // view constants int logSize{45}; - int footerSize{1}; + int footerSize{1}; // components Footer footer; @@ -126,8 +136,8 @@ class OSTreeTUI { ftxui::ScreenInteractive screen; ftxui::Component mainContainer; ftxui::Components commitComponents; - ftxui::Component commitList; - ftxui::Component tree; + ftxui::Component commitList; + ftxui::Component tree; ftxui::Component commitListComponent; ftxui::Component infoView; ftxui::Component filterView; @@ -135,10 +145,10 @@ class OSTreeTUI { ftxui::Component footerRenderer; ftxui::Component container; -public: + public: /** * @brief Print help page - * + * * @param caller argv[0] * @param errorMessage optional error message to print on top * @return 0, if no error message provided @@ -148,7 +158,7 @@ class OSTreeTUI { /** * @brief Print the application version - * + * * @return int */ static int showVersion(); diff --git a/src/core/commit.cpp b/src/core/commit.cpp index f09a738..1910d45 100644 --- a/src/core/commit.cpp +++ b/src/core/commit.cpp @@ -9,16 +9,16 @@ #include #include -#include // for Component, ComponentBase -#include "ftxui/component/component.hpp"// for Make -#include // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp -#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::WheelDown, Mouse::WheelUp -#include "ftxui/dom/elements.hpp" // for operator|, Element, size, vbox, EQUAL, HEIGHT, dbox, reflect, focus, inverted, nothing, select, vscroll_indicator, yflex, yframe -#include "ftxui/dom/node.hpp" // for Node -#include "ftxui/screen/box.hpp" // for Box +#include // for Component, ComponentBase #include +#include // for Event, Event::ArrowDown, Event::ArrowUp, Event::End, Event::Home, Event::PageDown, Event::PageUp #include // for ScreenInteractive -#include "ftxui/screen/color.hpp" // for Color +#include "ftxui/component/component.hpp" // for Make +#include "ftxui/component/mouse.hpp" // for Mouse, Mouse::WheelDown, Mouse::WheelUp +#include "ftxui/dom/elements.hpp" // for operator|, Element, size, vbox, EQUAL, HEIGHT, dbox, reflect, focus, inverted, nothing, select, vscroll_indicator, yflex, yframe +#include "ftxui/dom/node.hpp" // for Node +#include "ftxui/screen/box.hpp" // for Box +#include "ftxui/screen/color.hpp" // for Color #include "../util/cpplibostree.hpp" @@ -31,429 +31,431 @@ using namespace ftxui; /// From https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp Decorator PositionAndSize(int left, int top, int width, int height) { - return [=](Element element) { - element |= size(WIDTH, EQUAL, width); - element |= size(HEIGHT, EQUAL, height); - - auto padding_left = emptyElement() | size(WIDTH, EQUAL, left); - auto padding_top = emptyElement() | size(HEIGHT, EQUAL, top); - - return vbox({ - padding_top, - hbox({ - padding_left, - element, - }), - }); - }; + return [=](Element element) { + element |= size(WIDTH, EQUAL, width); + element |= size(HEIGHT, EQUAL, height); + + auto padding_left = emptyElement() | size(WIDTH, EQUAL, left); + auto padding_top = emptyElement() | size(HEIGHT, EQUAL, top); + + return vbox({ + padding_top, + hbox({ + padding_left, + element, + }), + }); + }; } -/// Partially inspired from https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp -Element DefaultRenderState(const WindowRenderState& state, ftxui::Color selectedColor = Color::White, bool dimmable = true) { - Element element = state.inner; - if (! dimmable) { - selectedColor = Color::White; - } - if (selectedColor == Color::White & dimmable) { - element |= dim; - } else { - element |= bold; - } - element |= color(selectedColor); - - element = window(text(state.title), element); - element |= clear_under; - - return element; +/// Partially inspired from +/// https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp +Element DefaultRenderState(const WindowRenderState& state, + ftxui::Color selectedColor = Color::White, + bool dimmable = true) { + Element element = state.inner; + if (!dimmable) { + selectedColor = Color::White; + } + if (selectedColor == Color::White & dimmable) { + element |= dim; + } else { + element |= bold; + } + element |= color(selectedColor); + + element = window(text(state.title), element); + element |= clear_under; + + return element; } /// Draggable commit window, including ostree-tui logic for overlap detection, etc. -/// Partially inspired from https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp +/// Partially inspired from +/// https://github.com/ArthurSonzogni/FTXUI/blob/main/src/ftxui/component/window.cpp class CommitComponentImpl : public ComponentBase, public WindowOptions { -public: - explicit CommitComponentImpl(int position, std::string commit, OSTreeTUI& ostreetui) : - commitPosition(position), - hash(std::move(commit)), - ostreetui(ostreetui), - commit(ostreetui.getOstreeRepo().getCommitList().at(hash)), - newVersion(this->commit.version), - drag_initial_y(position * COMMIT_WINDOW_HEIGHT), - drag_initial_x(1) - { - inner = Renderer([&] { - return vbox({ - text(ostreetui.getOstreeRepo().getCommitList().at(hash).subject), - text(std::format("{:%Y-%m-%d %T %Ez}", std::chrono::time_point_cast(ostreetui.getOstreeRepo().getCommitList().at(hash).timestamp))), - }); - }); - simpleCommit = inner; - Add(inner); - - title = hash.substr(0, 8); - top = drag_initial_y; - left = drag_initial_x; - width = COMMIT_WINDOW_WIDTH; - height = COMMIT_WINDOW_HEIGHT; - } - -private: - void resetWindow(bool positionReset = true) { - if (positionReset) { - left() = drag_initial_x; - top() = drag_initial_y; - } - width() = width_initial; - height() = height_initial; - // reset window contents - DetachAllChildren(); - Add(simpleCommit); - } - - void startPromotionWindow() { - left() = std::max(left(),-2); - width() = PROMOTION_WINDOW_WIDTH; - height() = PROMOTION_WINDOW_HEIGHT; - // change inner to promotion layout - simpleCommit = inner; - DetachAllChildren(); - Add(promotionView); - if (! Focused()) { - TakeFocus(); - // manually fix focus issues - // change to proper control layout if possible... - ostreetui.getScreen().Post(Event::ArrowDown); - ostreetui.getScreen().Post(Event::ArrowRight); - ostreetui.getScreen().Post(Event::ArrowDown); - ostreetui.getScreen().Post(Event::ArrowRight); - ostreetui.getScreen().Post(Event::ArrowUp); - } - } - - void executePromotion() { - // promote on the ostree repo - std::vector metadataStrings; - if (! newVersion.empty()) { - metadataStrings.push_back("version=" + newVersion); - } - ostreetui.promoteCommit(hash, ostreetui.getPromotionBranch(), metadataStrings, newSubject, true); - resetWindow(); - } - - void cancelPromotion() { - ostreetui.setPromotionMode(false); - resetWindow(); - } - - Element Render() final { - // check if promotion was started not from drag & drop, but from ostreetui - if (ostreetui.getInPromotionSelection() && ostreetui.getPromotionHash() == hash) { - if (! ostreetui.getPromotionBranch().empty()) { - startPromotionWindow(); - } else { - resetWindow(false); - } - } - - auto element = ComponentBase::Render(); - - const WindowRenderState state = { - element, - title(), - Active(), - drag_ - }; - - if (commitPosition == ostreetui.getSelectedCommit()) { // selected & not in promotion - element = render ? render(state) : DefaultRenderState(state, ostreetui.getBranchColorMap().at(commit.branch), ostreetui.getPromotionHash() != hash); - } else { - element = render ? render(state) : DefaultRenderState(state, Color::White, ostreetui.getPromotionHash() != hash); - } - - // Position and record the drawn area of the window. - element |= reflect(box_window_); - element |= PositionAndSize(left(), top() + ostreetui.getScrollOffset(), width(), height()); - element |= reflect(box_); - - return element; - } - - bool OnEvent(Event event) final { - if (ComponentBase::OnEvent(event)) { - return true; - } - - if (ostreetui.getInPromotionSelection()) { - // navigate promotion branches - if (event == Event::ArrowLeft) { - const long int it = std::find(ostreetui.getColumnToBranchMap().begin(), ostreetui.getColumnToBranchMap().end(), ostreetui.getPromotionBranch()) - ostreetui.getColumnToBranchMap().begin(); - ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at((it - 1) % ostreetui.getColumnToBranchMap().size())); - return true; - } - if (event == Event::ArrowRight) { - const long int it = std::find(ostreetui.getColumnToBranchMap().begin(), ostreetui.getColumnToBranchMap().end(), ostreetui.getPromotionBranch()) - ostreetui.getColumnToBranchMap().begin(); - ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at((it + 1) % ostreetui.getColumnToBranchMap().size())); - return true; - } - // promote - if (event == Event::Return) { - executePromotion(); - return true; - } - // cancel - if (event == Event::Escape) { - cancelPromotion(); - return true; - } - } - - if (!event.is_mouse()) { - return false; - } - - if (ostreetui.getInPromotionSelection() && ! drag_) { - return true; - } - - if (event.mouse().button == Mouse::Left) { - // update ostreetui - ostreetui.setSelectedCommit(commitPosition); - } - - mouse_hover_ = box_window_.Contain(event.mouse().x, event.mouse().y); - // potentially indicate mouse hover - //if (box_window_.Contain(event.mouse().x, event.mouse().y)) {} - - if (captured_mouse_) { - if (event.mouse().motion == Mouse::Released) { - // reset mouse - captured_mouse_ = nullptr; - // check if position matches branch & do something if it does - if (ostreetui.getPromotionBranch().empty()) { - ostreetui.setPromotionMode(false, hash); - resetWindow(); - } else { - ostreetui.setPromotionMode(true, hash); - } - return true; - } - - if (drag_) { - left() = event.mouse().x - drag_start_x - box_.x_min; - top() = event.mouse().y - drag_start_y - box_.y_min; - // potential promotion - ostreetui.setPromotionMode(true, hash, false); - // calculate which branch currently is hovered over - ostreetui.setPromotionBranch(""); - const int branch_pos = event.mouse().x / 2; - int count{0}; - for (const auto& [branch,visible] : ostreetui.getVisibleBranches()) { - if (visible) { - ++count; - } - if (count == branch_pos) { - ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at(event.mouse().x / 2 - 1)); - break; - } - } - } else { - // not promotion - ostreetui.setPromotionMode(false); - } - - // Clamp the window size. - width() = std::max(width(), static_cast(title().size() + 2)); - height() = std::max(height(), 2); - - return true; - } - - if (!mouse_hover_) { - return false; - } - - if (!CaptureMouse(event)) { - return true; - } - - if (event.mouse().button != Mouse::Left) { - return true; - } - if (event.mouse().motion != Mouse::Pressed) { - return true; - } - - TakeFocus(); - - captured_mouse_ = CaptureMouse(event); - if (!captured_mouse_) { - return true; - } - - drag_start_x = event.mouse().x - left() - box_.x_min; - drag_start_y = event.mouse().y - top() - box_.y_min; - - const bool drag_old = drag_; - drag_ = true; - if (!drag_old && drag_) { // if we start dragging - drag_initial_x = left(); - drag_initial_y = top(); - } - return true; - } - - // window specific members - Box box_; - Box box_window_; - - CapturedMouse captured_mouse_; - int drag_start_x = 0; - int drag_start_y = 0; - - int drag_initial_x; - int drag_initial_y; - int width_initial = COMMIT_WINDOW_WIDTH; - int height_initial = COMMIT_WINDOW_HEIGHT; - - bool mouse_hover_ = false; - bool drag_ = false; - - // ostree-tui specific members - int commitPosition; - std::string hash; - OSTreeTUI& ostreetui; - - // promotion view - cpplibostree::Commit commit; - std::string newSubject; - std::string newVersion; - Component simpleCommit = Renderer([] { - return text("error in commit window creation"); - }); - Component promotionView = Container::Vertical({ - Renderer([&] { - return vbox({ - text(""), - text(" Promote Commit...") | bold, - text(""), - text(" ☐ " + hash.substr(0, 8)) | bold, - }); - }), - Container::Horizontal({ - Renderer([&] { - return text(" ┆ subject: "); - }), - Input(&newSubject, "enter new subject...") | underlined - }), - // render version, if available - commit.version.empty() - ? Renderer([]{return filler();}) - : Container::Horizontal({ - Renderer([&] { - return text(" ┆ version: "); - }), - Input(&newVersion, commit.version) | underlined - }), - Renderer([&] { - return vbox({ - text(" ┆"), - text(" ┆ to branch:"), - text(" ☐ " + ostreetui.getPromotionBranch()) | bold, - text(" │") | bold - }); - }), - Container::Horizontal({ - Button(" Cancel ", [&] { - cancelPromotion(); - }) | color(Color::Red) | flex, - Button(" Promote ", [&] { - executePromotion(); - }) | color(Color::Green) | flex, - }) - }); + public: + explicit CommitComponentImpl(int position, std::string commit, OSTreeTUI& ostreetui) + : commitPosition(position), + hash(std::move(commit)), + ostreetui(ostreetui), + commit(ostreetui.getOstreeRepo().getCommitList().at(hash)), + newVersion(this->commit.version), + drag_initial_y(position * COMMIT_WINDOW_HEIGHT), + drag_initial_x(1) { + inner = Renderer([&] { + return vbox({ + text(ostreetui.getOstreeRepo().getCommitList().at(hash).subject), + text( + std::format("{:%Y-%m-%d %T %Ez}", + std::chrono::time_point_cast( + ostreetui.getOstreeRepo().getCommitList().at(hash).timestamp))), + }); + }); + simpleCommit = inner; + Add(inner); + + title = hash.substr(0, 8); + top = drag_initial_y; + left = drag_initial_x; + width = COMMIT_WINDOW_WIDTH; + height = COMMIT_WINDOW_HEIGHT; + } + + private: + void resetWindow(bool positionReset = true) { + if (positionReset) { + left() = drag_initial_x; + top() = drag_initial_y; + } + width() = width_initial; + height() = height_initial; + // reset window contents + DetachAllChildren(); + Add(simpleCommit); + } + + void startPromotionWindow() { + left() = std::max(left(), -2); + width() = PROMOTION_WINDOW_WIDTH; + height() = PROMOTION_WINDOW_HEIGHT; + // change inner to promotion layout + simpleCommit = inner; + DetachAllChildren(); + Add(promotionView); + if (!Focused()) { + TakeFocus(); + // manually fix focus issues + // change to proper control layout if possible... + ostreetui.getScreen().Post(Event::ArrowDown); + ostreetui.getScreen().Post(Event::ArrowRight); + ostreetui.getScreen().Post(Event::ArrowDown); + ostreetui.getScreen().Post(Event::ArrowRight); + ostreetui.getScreen().Post(Event::ArrowUp); + } + } + + void executePromotion() { + // promote on the ostree repo + std::vector metadataStrings; + if (!newVersion.empty()) { + metadataStrings.push_back("version=" + newVersion); + } + ostreetui.promoteCommit(hash, ostreetui.getPromotionBranch(), metadataStrings, newSubject, + true); + resetWindow(); + } + + void cancelPromotion() { + ostreetui.setPromotionMode(false); + resetWindow(); + } + + Element Render() final { + // check if promotion was started not from drag & drop, but from ostreetui + if (ostreetui.getInPromotionSelection() && ostreetui.getPromotionHash() == hash) { + if (!ostreetui.getPromotionBranch().empty()) { + startPromotionWindow(); + } else { + resetWindow(false); + } + } + + auto element = ComponentBase::Render(); + + const WindowRenderState state = {element, title(), Active(), drag_}; + + if (commitPosition == ostreetui.getSelectedCommit()) { // selected & not in promotion + element = + render ? render(state) + : DefaultRenderState(state, ostreetui.getBranchColorMap().at(commit.branch), + ostreetui.getPromotionHash() != hash); + } else { + element = render ? render(state) + : DefaultRenderState(state, Color::White, + ostreetui.getPromotionHash() != hash); + } + + // Position and record the drawn area of the window. + element |= reflect(box_window_); + element |= PositionAndSize(left(), top() + ostreetui.getScrollOffset(), width(), height()); + element |= reflect(box_); + + return element; + } + + bool OnEvent(Event event) final { + if (ComponentBase::OnEvent(event)) { + return true; + } + + if (ostreetui.getInPromotionSelection()) { + // navigate promotion branches + if (event == Event::ArrowLeft) { + const long int it = std::find(ostreetui.getColumnToBranchMap().begin(), + ostreetui.getColumnToBranchMap().end(), + ostreetui.getPromotionBranch()) - + ostreetui.getColumnToBranchMap().begin(); + ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at( + (it - 1) % ostreetui.getColumnToBranchMap().size())); + return true; + } + if (event == Event::ArrowRight) { + const long int it = std::find(ostreetui.getColumnToBranchMap().begin(), + ostreetui.getColumnToBranchMap().end(), + ostreetui.getPromotionBranch()) - + ostreetui.getColumnToBranchMap().begin(); + ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at( + (it + 1) % ostreetui.getColumnToBranchMap().size())); + return true; + } + // promote + if (event == Event::Return) { + executePromotion(); + return true; + } + // cancel + if (event == Event::Escape) { + cancelPromotion(); + return true; + } + } + + if (!event.is_mouse()) { + return false; + } + + if (ostreetui.getInPromotionSelection() && !drag_) { + return true; + } + + if (event.mouse().button == Mouse::Left) { + // update ostreetui + ostreetui.setSelectedCommit(commitPosition); + } + + mouse_hover_ = box_window_.Contain(event.mouse().x, event.mouse().y); + // potentially indicate mouse hover + // if (box_window_.Contain(event.mouse().x, event.mouse().y)) {} + + if (captured_mouse_) { + if (event.mouse().motion == Mouse::Released) { + // reset mouse + captured_mouse_ = nullptr; + // check if position matches branch & do something if it does + if (ostreetui.getPromotionBranch().empty()) { + ostreetui.setPromotionMode(false, hash); + resetWindow(); + } else { + ostreetui.setPromotionMode(true, hash); + } + return true; + } + + if (drag_) { + left() = event.mouse().x - drag_start_x - box_.x_min; + top() = event.mouse().y - drag_start_y - box_.y_min; + // potential promotion + ostreetui.setPromotionMode(true, hash, false); + // calculate which branch currently is hovered over + ostreetui.setPromotionBranch(""); + const int branch_pos = event.mouse().x / 2; + int count{0}; + for (const auto& [branch, visible] : ostreetui.getVisibleBranches()) { + if (visible) { + ++count; + } + if (count == branch_pos) { + ostreetui.setPromotionBranch( + ostreetui.getColumnToBranchMap().at(event.mouse().x / 2 - 1)); + break; + } + } + } else { + // not promotion + ostreetui.setPromotionMode(false); + } + + // Clamp the window size. + width() = std::max(width(), static_cast(title().size() + 2)); + height() = std::max(height(), 2); + + return true; + } + + if (!mouse_hover_) { + return false; + } + + if (!CaptureMouse(event)) { + return true; + } + + if (event.mouse().button != Mouse::Left) { + return true; + } + if (event.mouse().motion != Mouse::Pressed) { + return true; + } + + TakeFocus(); + + captured_mouse_ = CaptureMouse(event); + if (!captured_mouse_) { + return true; + } + + drag_start_x = event.mouse().x - left() - box_.x_min; + drag_start_y = event.mouse().y - top() - box_.y_min; + + const bool drag_old = drag_; + drag_ = true; + if (!drag_old && drag_) { // if we start dragging + drag_initial_x = left(); + drag_initial_y = top(); + } + return true; + } + + // window specific members + Box box_; + Box box_window_; + + CapturedMouse captured_mouse_; + int drag_start_x = 0; + int drag_start_y = 0; + + int drag_initial_x; + int drag_initial_y; + int width_initial = COMMIT_WINDOW_WIDTH; + int height_initial = COMMIT_WINDOW_HEIGHT; + + bool mouse_hover_ = false; + bool drag_ = false; + + // ostree-tui specific members + int commitPosition; + std::string hash; + OSTreeTUI& ostreetui; + + // promotion view + cpplibostree::Commit commit; + std::string newSubject; + std::string newVersion; + Component simpleCommit = Renderer([] { return text("error in commit window creation"); }); + Component promotionView = Container::Vertical( + {Renderer([&] { + return vbox({ + text(""), + text(" Promote Commit...") | bold, + text(""), + text(" ☐ " + hash.substr(0, 8)) | bold, + }); + }), + Container::Horizontal({Renderer([&] { return text(" ┆ subject: "); }), + Input(&newSubject, "enter new subject...") | underlined}), + // render version, if available + commit.version.empty() + ? Renderer([] { return filler(); }) + : Container::Horizontal({Renderer([&] { return text(" ┆ version: "); }), + Input(&newVersion, commit.version) | underlined}), + Renderer([&] { + return vbox({text(" ┆"), text(" ┆ to branch:"), + text(" ☐ " + ostreetui.getPromotionBranch()) | bold, text(" │") | bold}); + }), + Container::Horizontal({ + Button(" Cancel ", [&] { cancelPromotion(); }) | color(Color::Red) | flex, + Button(" Promote ", [&] { executePromotion(); }) | color(Color::Green) | flex, + })}); }; } // namespace ftxui::Component CommitComponent(int position, const std::string& commit, OSTreeTUI& ostreetui) { - return ftxui::Make(position, commit, ostreetui); + return ftxui::Make(position, commit, ostreetui); } -ftxui::Element commitRender(OSTreeTUI& ostreetui, const std::unordered_map& branchColorMap) { - using namespace ftxui; - - int scrollOffset = ostreetui.getScrollOffset(); - - // check empty commit list - if (ostreetui.getVisibleCommitViewMap().empty() || ostreetui.getVisibleBranches().empty()) { - return color(Color::RedLight, text(" no commits to be shown ") | bold | center); - } - - // stores the dedicated tree-column of each branch, -1 meaning not displayed yet - std::unordered_map usedBranches{}; - for (const auto& branchPair : ostreetui.getVisibleBranches()) { - if (branchPair.second) { - usedBranches[branchPair.first] = -1; - } - } - int nextAvailableSpace = static_cast(usedBranches.size() - 1); - - // - RENDER - - // left tree, right commits - Elements treeElements{}; - Elements commElements{}; - - ostreetui.getColumnToBranchMap().clear(); - for (const auto& visibleCommitIndex : ostreetui.getVisibleCommitViewMap()) { - const cpplibostree::Commit commit = ostreetui.getOstreeRepo().getCommitList().at(visibleCommitIndex); - // branch head if it is first branch usage - const std::string relevantBranch = commit.branch; - if (usedBranches.at(relevantBranch) == -1) { - ostreetui.getColumnToBranchMap().push_back(relevantBranch); - usedBranches.at(relevantBranch) = nextAvailableSpace--; - } - // commit - if (scrollOffset++ >= 0) { - treeElements.push_back(addTreeLine(RenderTree::TREE_LINE_NODE, commit, usedBranches, branchColorMap)); - } - for (int i{0}; i < 3; i++) { - if (scrollOffset++ >= 0) { - treeElements.push_back(addTreeLine(RenderTree::TREE_LINE_TREE, commit, usedBranches, branchColorMap)); - } - } - } - std::reverse(ostreetui.getColumnToBranchMap().begin(), ostreetui.getColumnToBranchMap().end()); - - return hbox({ - vbox(std::move(treeElements)), - vbox(std::move(commElements)) - }); +ftxui::Element commitRender(OSTreeTUI& ostreetui, + const std::unordered_map& branchColorMap) { + using namespace ftxui; + + int scrollOffset = ostreetui.getScrollOffset(); + + // check empty commit list + if (ostreetui.getVisibleCommitViewMap().empty() || ostreetui.getVisibleBranches().empty()) { + return color(Color::RedLight, text(" no commits to be shown ") | bold | center); + } + + // stores the dedicated tree-column of each branch, -1 meaning not displayed yet + std::unordered_map usedBranches{}; + for (const auto& branchPair : ostreetui.getVisibleBranches()) { + if (branchPair.second) { + usedBranches[branchPair.first] = -1; + } + } + int nextAvailableSpace = static_cast(usedBranches.size() - 1); + + // - RENDER - + // left tree, right commits + Elements treeElements{}; + Elements commElements{}; + + ostreetui.getColumnToBranchMap().clear(); + for (const auto& visibleCommitIndex : ostreetui.getVisibleCommitViewMap()) { + const cpplibostree::Commit commit = + ostreetui.getOstreeRepo().getCommitList().at(visibleCommitIndex); + // branch head if it is first branch usage + const std::string relevantBranch = commit.branch; + if (usedBranches.at(relevantBranch) == -1) { + ostreetui.getColumnToBranchMap().push_back(relevantBranch); + usedBranches.at(relevantBranch) = nextAvailableSpace--; + } + // commit + if (scrollOffset++ >= 0) { + treeElements.push_back( + addTreeLine(RenderTree::TREE_LINE_NODE, commit, usedBranches, branchColorMap)); + } + for (int i{0}; i < 3; i++) { + if (scrollOffset++ >= 0) { + treeElements.push_back( + addTreeLine(RenderTree::TREE_LINE_TREE, commit, usedBranches, branchColorMap)); + } + } + } + std::reverse(ostreetui.getColumnToBranchMap().begin(), ostreetui.getColumnToBranchMap().end()); + + return hbox({vbox(std::move(treeElements)), vbox(std::move(commElements))}); } ftxui::Element addTreeLine(const RenderTree& treeLineType, - const cpplibostree::Commit& commit, - const std::unordered_map& usedBranches, - const std::unordered_map& branchColorMap) { - using namespace ftxui; - - const std::string relevantBranch = commit.branch; - // create an empty branch tree line - Elements tree(usedBranches.size(), text(COMMIT_NONE)); - - // populate tree with all displayed branches - for (const auto& branch : usedBranches) { - if (branch.second == -1) { - continue; - } - - if (treeLineType == RenderTree::TREE_LINE_TREE || (treeLineType == RenderTree::TREE_LINE_IGNORE_BRANCH && branch.first != relevantBranch)) { - tree.at(branch.second) = (text(COMMIT_TREE) | color(branchColorMap.at(branch.first))); - } else if (treeLineType == RenderTree::TREE_LINE_NODE) { - if (branch.first == relevantBranch) { - tree.at(branch.second) = (text(COMMIT_NODE) | color(branchColorMap.at(branch.first))); - } else { - tree.at(branch.second) = (text(COMMIT_TREE) | color(branchColorMap.at(branch.first))); - } - } + const cpplibostree::Commit& commit, + const std::unordered_map& usedBranches, + const std::unordered_map& branchColorMap) { + using namespace ftxui; + + const std::string relevantBranch = commit.branch; + // create an empty branch tree line + Elements tree(usedBranches.size(), text(COMMIT_NONE)); + + // populate tree with all displayed branches + for (const auto& branch : usedBranches) { + if (branch.second == -1) { + continue; + } + + if (treeLineType == RenderTree::TREE_LINE_TREE || + (treeLineType == RenderTree::TREE_LINE_IGNORE_BRANCH && + branch.first != relevantBranch)) { + tree.at(branch.second) = (text(COMMIT_TREE) | color(branchColorMap.at(branch.first))); + } else if (treeLineType == RenderTree::TREE_LINE_NODE) { + if (branch.first == relevantBranch) { + tree.at(branch.second) = + (text(COMMIT_NODE) | color(branchColorMap.at(branch.first))); + } else { + tree.at(branch.second) = + (text(COMMIT_TREE) | color(branchColorMap.at(branch.first))); + } + } } return hbox(std::move(tree)); diff --git a/src/core/commit.hpp b/src/core/commit.hpp index d455132..0f0c18e 100644 --- a/src/core/commit.hpp +++ b/src/core/commit.hpp @@ -15,66 +15,65 @@ #include #include -#include "ftxui/component/component_base.hpp" #include +#include "ftxui/component/component_base.hpp" #include "../util/cpplibostree.hpp" class OSTreeTUI; namespace CommitRender { - // UI characters - constexpr std::string COMMIT_NODE {" ☐"}; - constexpr std::string COMMIT_TREE {" │"}; - constexpr std::string COMMIT_NONE {" "}; - // window dimensions - constexpr int COMMIT_WINDOW_HEIGHT {4}; - constexpr int COMMIT_WINDOW_WIDTH {32}; - constexpr int PROMOTION_WINDOW_HEIGHT {COMMIT_WINDOW_HEIGHT + 11}; - constexpr int PROMOTION_WINDOW_WIDTH {COMMIT_WINDOW_WIDTH + 8}; - // render tree types - enum RenderTree : uint8_t { - TREE_LINE_NODE, // ☐ | | - TREE_LINE_TREE, // | | | - TREE_LINE_IGNORE_BRANCH // | | - }; +// UI characters +constexpr std::string COMMIT_NODE{" ☐"}; +constexpr std::string COMMIT_TREE{" │"}; +constexpr std::string COMMIT_NONE{" "}; +// window dimensions +constexpr int COMMIT_WINDOW_HEIGHT{4}; +constexpr int COMMIT_WINDOW_WIDTH{32}; +constexpr int PROMOTION_WINDOW_HEIGHT{COMMIT_WINDOW_HEIGHT + 11}; +constexpr int PROMOTION_WINDOW_WIDTH{COMMIT_WINDOW_WIDTH + 8}; +// render tree types +enum RenderTree : uint8_t { + TREE_LINE_NODE, // ☐ | | + TREE_LINE_TREE, // | | | + TREE_LINE_IGNORE_BRANCH // | | +}; - /** - * @brief Creates a window, containing a hash and some of its details. - * The window has a pre-defined position and snaps back to it, - * after being dragged & let go. When hovering over a branch, - * defined in `columnToBranchMap`, the window expands to a commit - * promotion window. - * To be used with other windows, use a ftxui::Component::Stacked. - * - * @return Component - */ - ftxui::Component CommitComponent(int position, const std::string& commit, OSTreeTUI& ostreetui); +/** + * @brief Creates a window, containing a hash and some of its details. + * The window has a pre-defined position and snaps back to it, + * after being dragged & let go. When hovering over a branch, + * defined in `columnToBranchMap`, the window expands to a commit + * promotion window. + * To be used with other windows, use a ftxui::Component::Stacked. + * + * @return Component + */ +ftxui::Component CommitComponent(int position, const std::string& commit, OSTreeTUI& ostreetui); - /** - * @brief create a Renderer for the commit section - * - * @param ostreetui OSTreeTUI containing OSTreeRepo and UI info - * @param branchColorMap Map from branch to its display color - * @param selectedCommit Commit that should be marked as selected - * @return ftxui::Element - */ - ftxui::Element commitRender(OSTreeTUI& ostreetui, const std::unordered_map& branchColorMap); +/** + * @brief create a Renderer for the commit section + * + * @param ostreetui OSTreeTUI containing OSTreeRepo and UI info + * @param branchColorMap Map from branch to its display color + * @param selectedCommit Commit that should be marked as selected + * @return ftxui::Element + */ +ftxui::Element commitRender(OSTreeTUI& ostreetui, + const std::unordered_map& branchColorMap); - /** - * @brief build a commit-tree line - * - * @param treeLineType type of commit-tree - * @param commit commit to render / get info from - * @param usedBranches branches to render - * @param branchColorMap branch colors - * @return ftxui::Element commit-tree line - */ - ftxui::Element addTreeLine( - const RenderTree& treeLineType, - const cpplibostree::Commit& commit, - const std::unordered_map& usedBranches, - const std::unordered_map& branchColorMap); +/** + * @brief build a commit-tree line + * + * @param treeLineType type of commit-tree + * @param commit commit to render / get info from + * @param usedBranches branches to render + * @param branchColorMap branch colors + * @return ftxui::Element commit-tree line + */ +ftxui::Element addTreeLine(const RenderTree& treeLineType, + const cpplibostree::Commit& commit, + const std::unordered_map& usedBranches, + const std::unordered_map& branchColorMap); -} // namespace CommitRender +} // namespace CommitRender diff --git a/src/core/footer.cpp b/src/core/footer.cpp index ed7ace0..8faf514 100644 --- a/src/core/footer.cpp +++ b/src/core/footer.cpp @@ -3,19 +3,20 @@ #include "footer.hpp" ftxui::Element Footer::footerRender() { - using namespace ftxui; - - return hbox({ - text("OSTree TUI") | bold | hyperlink("https://github.com/AP-Sensing/ostree-tui"), - separator(), - text(content) | (content == DEFAULT_CONTENT ? color(Color::White) : color(Color::YellowLight)), - }); + using namespace ftxui; + + return hbox({ + text("OSTree TUI") | bold | hyperlink("https://github.com/AP-Sensing/ostree-tui"), + separator(), + text(content) | + (content == DEFAULT_CONTENT ? color(Color::White) : color(Color::YellowLight)), + }); } void Footer::resetContent() { - content = DEFAULT_CONTENT; + content = DEFAULT_CONTENT; } void Footer::setContent(std::string content) { - this->content = content; + this->content = content; } diff --git a/src/core/footer.hpp b/src/core/footer.hpp index ba978e0..de3cf89 100644 --- a/src/core/footer.hpp +++ b/src/core/footer.hpp @@ -4,11 +4,10 @@ | keyboard shortcuts info. |___________________________________________________________*/ - class Footer { -public: + public: Footer() = default; - + /// reset footer text to default string void resetContent(); @@ -18,8 +17,9 @@ class Footer { // Setter void setContent(std::string content); -private: - const std::string DEFAULT_CONTENT {" || Alt+Q : Quit || Alt+R : Refresh || Alt+C : Copy commit hash || Alt+P : Promote Commit "}; - std::string content {DEFAULT_CONTENT}; - + private: + const std::string DEFAULT_CONTENT{ + " || Alt+Q : Quit || Alt+R : Refresh || Alt+C : Copy commit hash || Alt+P : Promote " + "Commit "}; + std::string content{DEFAULT_CONTENT}; }; diff --git a/src/core/manager.cpp b/src/core/manager.cpp index f49725b..876ed40 100644 --- a/src/core/manager.cpp +++ b/src/core/manager.cpp @@ -1,12 +1,12 @@ #include "manager.hpp" +#include #include #include -#include #include "ftxui/component/component.hpp" // for Renderer, ResizableSplitBottom, ResizableSplitLeft, ResizableSplitRight, ResizableSplitTop -#include "ftxui/component/event.hpp" // for Event -#include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border +#include "ftxui/component/event.hpp" // for Event +#include "ftxui/dom/elements.hpp" // for Element, operator|, text, center, border #include "../util/cpplibostree.hpp" @@ -14,110 +14,91 @@ // Manager -Manager::Manager(OSTreeTUI& ostreetui, const ftxui::Component& infoView, const ftxui::Component& filterView) : ostreetui(ostreetui) { - using namespace ftxui; - - tabSelection = Menu(&tab_entries, &tab_index, MenuOption::HorizontalAnimated()); - - tabContent = Container::Tab({ - infoView, - filterView - }, - &tab_index); - - managerRenderer = Container::Vertical({ - tabSelection, - tabContent, - Renderer([] {return vbox({filler()}) | flex;}), // push elements apart - Renderer([&] { - Elements branches; - for (size_t i{ostreetui.getColumnToBranchMap().size()}; i > 0; i--) { - std::string branch = ostreetui.getColumnToBranchMap().at(i - 1); - std::string line = "――☐――― " + branch; - branches.push_back(text(line) | color(ostreetui.getBranchColorMap().at(branch))); - } - return vbox(branches); - }) - }); +Manager::Manager(OSTreeTUI& ostreetui, + const ftxui::Component& infoView, + const ftxui::Component& filterView) + : ostreetui(ostreetui) { + using namespace ftxui; + + tabSelection = Menu(&tab_entries, &tab_index, MenuOption::HorizontalAnimated()); + + tabContent = Container::Tab({infoView, filterView}, &tab_index); + + managerRenderer = Container::Vertical( + {tabSelection, tabContent, + Renderer([] { return vbox({filler()}) | flex; }), // push elements apart + Renderer([&] { + Elements branches; + for (size_t i{ostreetui.getColumnToBranchMap().size()}; i > 0; i--) { + std::string branch = ostreetui.getColumnToBranchMap().at(i - 1); + std::string line = "――☐――― " + branch; + branches.push_back(text(line) | color(ostreetui.getBranchColorMap().at(branch))); + } + return vbox(branches); + })}); } ftxui::Component Manager::getManagerRenderer() { - return managerRenderer; + return managerRenderer; } const int& Manager::getTabIndex() const { - return tab_index; + return tab_index; } // BranchBoxManager -BranchBoxManager::BranchBoxManager(cpplibostree::OSTreeRepo& repo, std::unordered_map& visibleBranches) { +BranchBoxManager::BranchBoxManager(cpplibostree::OSTreeRepo& repo, + std::unordered_map& visibleBranches) { using namespace ftxui; - // branch visibility - for (const auto& branch : repo.getBranches()) { - branchBoxes->Add(Checkbox(branch, &(visibleBranches.at(branch)))); - } + // branch visibility + for (const auto& branch : repo.getBranches()) { + branchBoxes->Add(Checkbox(branch, &(visibleBranches.at(branch)))); + } } -ftxui::Element BranchBoxManager::branchBoxRender(){ - using namespace ftxui; - - // branch filter - Elements bfb_elements = { - text(L"branches:") | bold, - filler(), - branchBoxes->Render() | vscroll_indicator | frame | size(HEIGHT, LESS_THAN, 10), - }; - return vbox(bfb_elements); +ftxui::Element BranchBoxManager::branchBoxRender() { + using namespace ftxui; + + // branch filter + Elements bfb_elements = { + text(L"branches:") | bold, + filler(), + branchBoxes->Render() | vscroll_indicator | frame | size(HEIGHT, LESS_THAN, 10), + }; + return vbox(bfb_elements); } // CommitInfoManager ftxui::Element CommitInfoManager::renderInfoView(const cpplibostree::Commit& displayCommit) { - using namespace ftxui; - - // selected commit info - Elements signatures; - for (const auto& signature : displayCommit.signatures) { - std::string ts = std::format("{:%Y-%m-%d %T %Ez}", - std::chrono::time_point_cast(signature.timestamp)); - signatures.push_back(vbox({ - hbox({ - text("‣ "), - text(signature.pubkeyAlgorithm) | bold, - text(" signature") - }), - text(" with key ID " + signature.fingerprint), - text(" made " + ts) - })); - } - return vbox({ - text(" Subject:") | color(Color::Green), - paragraph(displayCommit.subject) | color(Color::White), - filler(), - text(" Hash: ") | color(Color::Green), - text(displayCommit.hash), - filler(), - text(" Date: ") | color(Color::Green), - text(std::format("{:%Y-%m-%d %T %Ez}", - std::chrono::time_point_cast(displayCommit.timestamp))), - filler(), - // TODO insert version, only if exists - displayCommit.version.empty() - ? filler() - : text(" Version: ") | color(Color::Green), - displayCommit.version.empty() - ? filler() - : text(displayCommit.version), - text(" Parent: ") | color(Color::Green), - text(displayCommit.parent), - filler(), - text(" Checksum: ") | color(Color::Green), - text(displayCommit.contentChecksum), - filler(), - displayCommit.signatures.size() > 0 ? text(" Signatures: ") | color(Color::Green) : text(""), - vbox(signatures), - filler() - }); + using namespace ftxui; + + // selected commit info + Elements signatures; + for (const auto& signature : displayCommit.signatures) { + std::string ts = + std::format("{:%Y-%m-%d %T %Ez}", + std::chrono::time_point_cast(signature.timestamp)); + signatures.push_back( + vbox({hbox({text("‣ "), text(signature.pubkeyAlgorithm) | bold, text(" signature")}), + text(" with key ID " + signature.fingerprint), text(" made " + ts)})); + } + return vbox( + {text(" Subject:") | color(Color::Green), + paragraph(displayCommit.subject) | color(Color::White), filler(), + text(" Hash: ") | color(Color::Green), text(displayCommit.hash), filler(), + text(" Date: ") | color(Color::Green), + text(std::format("{:%Y-%m-%d %T %Ez}", std::chrono::time_point_cast( + displayCommit.timestamp))), + filler(), + // TODO insert version, only if exists + displayCommit.version.empty() ? filler() : text(" Version: ") | color(Color::Green), + displayCommit.version.empty() ? filler() : text(displayCommit.version), + text(" Parent: ") | color(Color::Green), text(displayCommit.parent), filler(), + text(" Checksum: ") | color(Color::Green), text(displayCommit.contentChecksum), filler(), + displayCommit.signatures.size() > 0 ? text(" Signatures: ") | color(Color::Green) + : text(""), + vbox(signatures), filler()}); } diff --git a/src/core/manager.hpp b/src/core/manager.hpp index 5e41dba..8719e8c 100644 --- a/src/core/manager.hpp +++ b/src/core/manager.hpp @@ -18,16 +18,16 @@ class OSTreeTUI; /// Interchangeable View class Manager { -public: - Manager(OSTreeTUI& ostreetui, const ftxui::Component& infoView, const ftxui::Component& filterView); + public: + Manager(OSTreeTUI& ostreetui, + const ftxui::Component& infoView, + const ftxui::Component& filterView); -private: + private: OSTreeTUI& ostreetui; int tab_index{0}; - std::vector tab_entries = { - " Info ", " Filter " - }; + std::vector tab_entries = {" Info ", " Filter "}; // because the combination of all interchangeable views is very simple, // we can (in contrast to the other ones) render this one here @@ -35,33 +35,34 @@ class Manager { ftxui::Component tabSelection; ftxui::Component tabContent; -public: + public: ftxui::Component getManagerRenderer(); const int& getTabIndex() const; }; class CommitInfoManager { -public: + public: /** * @brief Build the info view Element. - * + * * @param displayCommit Commit to display the information of. - * @return ftxui::Element + * @return ftxui::Element */ static ftxui::Element renderInfoView(const cpplibostree::Commit& displayCommit); }; class BranchBoxManager { -public: + public: ftxui::Component branchBoxes = ftxui::Container::Vertical({}); -public: - BranchBoxManager(cpplibostree::OSTreeRepo& repo, std::unordered_map& visibleBranches); - + public: + BranchBoxManager(cpplibostree::OSTreeRepo& repo, + std::unordered_map& visibleBranches); + /** * @brief Build the branch box Element. - * - * @return ftxui::Element + * + * @return ftxui::Element */ ftxui::Element branchBoxRender(); }; diff --git a/src/main.cpp b/src/main.cpp index 29eb9be..98d6f66 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,56 +6,57 @@ /** * @brief Parse all options listed behind an argument - * + * * @param args argument list * @param find argument (as vector for multiple arg version support, like -h & --help) * @return vector of options */ -std::vector getArgOptions(const std::vector& args, const std::vector& find) { - std::vector out; - for (const auto& arg : find) { - auto argIndex = std::find(args.begin(), args.end(), arg); - if (argIndex == args.end()) { - continue; - } - while (++argIndex != args.end()) { - if ((*argIndex).at(0) == '-') { - // arrived at next argument -> stop - break; - } - out.push_back(*argIndex); - } - } - return out; +std::vector getArgOptions(const std::vector& args, + const std::vector& find) { + std::vector out; + for (const auto& arg : find) { + auto argIndex = std::find(args.begin(), args.end(), arg); + if (argIndex == args.end()) { + continue; + } + while (++argIndex != args.end()) { + if ((*argIndex).at(0) == '-') { + // arrived at next argument -> stop + break; + } + out.push_back(*argIndex); + } + } + return out; } /// Check if argument exists in argument list bool argExists(const std::vector& args, std::string arg) { - return std::find(args.begin(), args.end(), arg) != args.end(); + return std::find(args.begin(), args.end(), arg) != args.end(); } /// main for argument parsing and OSTree TUI call int main(int argc, const char** argv) { - // too few amount of arguments - if (argc <= 1) { - return OSTreeTUI::showHelp(argc <= 0 ? "ostree" : argv[0], "no repository provided"); - } + // too few amount of arguments + if (argc <= 1) { + return OSTreeTUI::showHelp(argc <= 0 ? "ostree" : argv[0], "no repository provided"); + } - std::vector args(argv + 1, argv + argc); - // -h, --help - if (argExists(args,"-h") || argExists(args,"--help")) { - return OSTreeTUI::showHelp(argv[0]); - } - // -v, --version - if (argExists(args,"-v") || argExists(args,"--version")) { - return OSTreeTUI::showVersion(); - } - // assume ostree repository path as first argument - std::string repo = args.at(0); - // -r, --refs - std::vector startupBranches = getArgOptions(args, {"-r", "--refs"}); + std::vector args(argv + 1, argv + argc); + // -h, --help + if (argExists(args, "-h") || argExists(args, "--help")) { + return OSTreeTUI::showHelp(argv[0]); + } + // -v, --version + if (argExists(args, "-v") || argExists(args, "--version")) { + return OSTreeTUI::showVersion(); + } + // assume ostree repository path as first argument + std::string repo = args.at(0); + // -r, --refs + std::vector startupBranches = getArgOptions(args, {"-r", "--refs"}); - // OSTree TUI - OSTreeTUI ostreetui(repo, startupBranches); - return ostreetui.run(); + // OSTree TUI + OSTreeTUI ostreetui(repo, startupBranches); + return ostreetui.run(); } diff --git a/src/util/cpplibostree.cpp b/src/util/cpplibostree.cpp index e97c4f5..24bcbcb 100644 --- a/src/util/cpplibostree.cpp +++ b/src/util/cpplibostree.cpp @@ -8,329 +8,338 @@ #include #include // C -#include +#include #include #include #include -#include +#include namespace cpplibostree { - OSTreeRepo::OSTreeRepo(std::string path): - repoPath(std::move(path)), - commitList({}), - branches({}) { - updateData(); +OSTreeRepo::OSTreeRepo(std::string path) : repoPath(std::move(path)), commitList({}), branches({}) { + updateData(); +} + +bool OSTreeRepo::updateData() { + // parse branches + std::string branchString = getBranchesAsString(); + std::stringstream bss(branchString); + std::string word; + while (bss >> word) { + branches.push_back(word); } - bool OSTreeRepo::updateData() { - // parse branches - std::string branchString = getBranchesAsString(); - std::stringstream bss(branchString); - std::string word; - while (bss >> word) { - branches.push_back(word); - } + // parse commits + commitList = parseCommitsAllBranches(); - // parse commits - commitList = parseCommitsAllBranches(); + return true; +} - return true; - } +// METHODS - // METHODS - - OstreeRepo* OSTreeRepo::_c() { - // open repo - GError *error{nullptr}; - OstreeRepo *repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); - if (repo == nullptr) { - g_printerr("Error opening repository: %s\n", error->message); - g_error_free(error); - return nullptr; - } - return repo; +OstreeRepo* OSTreeRepo::_c() { + // open repo + GError* error{nullptr}; + OstreeRepo* repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); + if (repo == nullptr) { + g_printerr("Error opening repository: %s\n", error->message); + g_error_free(error); + return nullptr; } - - const std::string& OSTreeRepo::getRepoPath() const { - return repoPath; + return repo; +} + +const std::string& OSTreeRepo::getRepoPath() const { + return repoPath; +} + +const CommitList& OSTreeRepo::getCommitList() const { + return commitList; +} + +const std::vector& OSTreeRepo::getBranches() const { + return branches; +} + +bool OSTreeRepo::isCommitSigned(const Commit& commit) { + return commit.signatures.size() > 0; +} + +Commit OSTreeRepo::parseCommit(GVariant* variant, + const std::string& branch, + const std::string& hash) { + Commit commit; + + const gchar* subject{nullptr}; + const gchar* body{nullptr}; + const gchar* version{nullptr}; + guint64 timestamp{0}; + g_autofree char* parent{nullptr}; + + // see OSTREE_COMMIT_GVARIANT_FORMAT + g_variant_get(variant, "(a{sv}aya(say)&s&stayay)", nullptr, nullptr, nullptr, &subject, &body, + ×tamp, nullptr, nullptr); + assert(body); + assert(timestamp); + + // timestamp + timestamp = GUINT64_FROM_BE(timestamp); + commit.timestamp = Timepoint(std::chrono::seconds(timestamp)); + + // parent + parent = ostree_commit_get_parent(variant); + if (parent) { + commit.parent = parent; + } else { + commit.parent = "(no parent)"; } - const CommitList& OSTreeRepo::getCommitList() const { - return commitList; + // content checksum + g_autofree char* contents = ostree_commit_get_content_checksum(variant); + assert(contents); + commit.contentChecksum = contents; + + // version + g_autoptr(GVariant) metadata = NULL; + const char* ret = NULL; + metadata = g_variant_get_child_value(variant, 0); + if (g_variant_lookup(metadata, OSTREE_COMMIT_META_KEY_VERSION, "&s", &ret)) { + version = g_strdup(ret); + commit.version = version; } - const std::vector& OSTreeRepo::getBranches() const { - return branches; + // subject + if (subject[0]) { + std::string val = subject; + commit.subject = val; + } else { + commit.subject = "(no subject)"; } - bool OSTreeRepo::isCommitSigned(const Commit& commit) { - return commit.signatures.size() > 0; + // body + if (body[0]) { + commit.body = body; } - Commit OSTreeRepo::parseCommit(GVariant *variant, const std::string& branch, const std::string& hash) { - Commit commit; - - const gchar *subject {nullptr}; - const gchar *body {nullptr}; - const gchar *version {nullptr}; - guint64 timestamp {0}; - g_autofree char *parent {nullptr}; - - // see OSTREE_COMMIT_GVARIANT_FORMAT - g_variant_get(variant, "(a{sv}aya(say)&s&stayay)", nullptr, nullptr, nullptr, &subject, &body, ×tamp, nullptr, nullptr); - assert(body); - assert(timestamp); - - // timestamp - timestamp = GUINT64_FROM_BE(timestamp); - commit.timestamp = Timepoint(std::chrono::seconds(timestamp)); - - // parent - parent = ostree_commit_get_parent(variant); - if (parent) { - commit.parent = parent; - } else { - commit.parent = "(no parent)"; - } - - // content checksum - g_autofree char *contents = ostree_commit_get_content_checksum(variant); - assert(contents); - commit.contentChecksum = contents; - - // version - g_autoptr (GVariant) metadata = NULL; - const char *ret = NULL; - metadata = g_variant_get_child_value(variant, 0); - if (g_variant_lookup(metadata, OSTREE_COMMIT_META_KEY_VERSION, "&s", &ret)) { - version = g_strdup(ret); - commit.version = version; - } - - // subject - if (subject[0]) { - std::string val = subject; - commit.subject = val; - } else { - commit.subject = "(no subject)"; - } - - // body - if (body[0]) { - commit.body = body; - } - - commit.branch = branch; - commit.hash = hash; - - // Signatures ___ refactor into own method - // open repo - GError *error = nullptr; - OstreeRepo *repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); - if (repo == nullptr) { - g_printerr("Error opening repository: %s\n", error->message); - g_error_free(error); - assert(repo); - } - // see ostree print_object for reference - g_autoptr(OstreeGpgVerifyResult) result = nullptr; - g_autoptr(GError) local_error = nullptr; - result = ostree_repo_verify_commit_ext (repo, commit.hash.c_str(), nullptr, nullptr, nullptr, - &local_error); - if (g_error_matches (local_error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE) || local_error != nullptr) { - /* Ignore */ - } else { - assert(result); - guint n_sigs = ostree_gpg_verify_result_count_all (result); - // parse all found signatures - for (guint ii = 0; ii < n_sigs; ii++) { - g_autoptr(GVariant) variant = nullptr; - variant = ostree_gpg_verify_result_get_all (result, ii); - // see ostree_gpg_verify_result_describe_variant for reference - gint64 timestamp {0}; - gint64 exp_timestamp {0}; - gint64 key_exp_timestamp {0}; - gint64 key_exp_timestamp_primary{0}; - const char *fingerprint {nullptr}; - const char *fingerprintPrimary {nullptr}; - const char *pubkey_algo {nullptr}; - const char *user_name {nullptr}; - const char *user_email {nullptr}; - gboolean valid {false}; - gboolean sigExpired {false}; - gboolean keyExpired {false}; - gboolean keyRevoked {false}; - gboolean keyMissing {false}; - - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_VALID, "b", &valid); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_SIG_EXPIRED, "b", &sigExpired); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXPIRED, "b", &keyExpired); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_REVOKED, "b", &keyRevoked); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_MISSING, "b", &keyMissing); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT, "&s", &fingerprint); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT_PRIMARY, "&s", &fingerprintPrimary); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_TIMESTAMP, "x", ×tamp); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_EXP_TIMESTAMP, "x", &exp_timestamp); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_PUBKEY_ALGO_NAME, "&s", &pubkey_algo); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_USER_NAME, "&s", &user_name); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_USER_EMAIL, "&s", &user_email); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP, "x", &key_exp_timestamp); - g_variant_get_child (variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP_PRIMARY, "x", &key_exp_timestamp_primary); - - // create signature struct - Signature sig; - - sig.valid = valid; - sig.sigExpired = sigExpired; - sig.keyExpired = keyExpired; - sig.keyRevoked = keyRevoked; - sig.keyMissing = keyMissing; - sig.fingerprint = fingerprint; - sig.fingerprintPrimary = fingerprintPrimary; - sig.timestamp = Timepoint(std::chrono::seconds(timestamp)); - sig.expireTimestamp = Timepoint(std::chrono::seconds(exp_timestamp)); - sig.pubkeyAlgorithm = pubkey_algo; - sig.username = user_name; - sig.usermail = user_email; - sig.keyExpireTimestamp = Timepoint(std::chrono::seconds(key_exp_timestamp)); - sig.keyExpireTimestampPrimary = Timepoint(std::chrono::seconds(key_exp_timestamp_primary)); - - commit.signatures.push_back(std::move(sig)); - } + commit.branch = branch; + commit.hash = hash; + + // Signatures ___ refactor into own method + // open repo + GError* error = nullptr; + OstreeRepo* repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); + if (repo == nullptr) { + g_printerr("Error opening repository: %s\n", error->message); + g_error_free(error); + assert(repo); + } + // see ostree print_object for reference + g_autoptr(OstreeGpgVerifyResult) result = nullptr; + g_autoptr(GError) local_error = nullptr; + result = ostree_repo_verify_commit_ext(repo, commit.hash.c_str(), nullptr, nullptr, nullptr, + &local_error); + if (g_error_matches(local_error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE) || + local_error != nullptr) { + /* Ignore */ + } else { + assert(result); + guint n_sigs = ostree_gpg_verify_result_count_all(result); + // parse all found signatures + for (guint ii = 0; ii < n_sigs; ii++) { + g_autoptr(GVariant) variant = nullptr; + variant = ostree_gpg_verify_result_get_all(result, ii); + // see ostree_gpg_verify_result_describe_variant for reference + gint64 timestamp{0}; + gint64 exp_timestamp{0}; + gint64 key_exp_timestamp{0}; + gint64 key_exp_timestamp_primary{0}; + const char* fingerprint{nullptr}; + const char* fingerprintPrimary{nullptr}; + const char* pubkey_algo{nullptr}; + const char* user_name{nullptr}; + const char* user_email{nullptr}; + gboolean valid{false}; + gboolean sigExpired{false}; + gboolean keyExpired{false}; + gboolean keyRevoked{false}; + gboolean keyMissing{false}; + + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_VALID, "b", &valid); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_SIG_EXPIRED, "b", &sigExpired); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXPIRED, "b", &keyExpired); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_REVOKED, "b", &keyRevoked); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_MISSING, "b", &keyMissing); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT, "&s", &fingerprint); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_FINGERPRINT_PRIMARY, "&s", + &fingerprintPrimary); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_TIMESTAMP, "x", ×tamp); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_EXP_TIMESTAMP, "x", + &exp_timestamp); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_PUBKEY_ALGO_NAME, "&s", + &pubkey_algo); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_USER_NAME, "&s", &user_name); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_USER_EMAIL, "&s", &user_email); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP, "x", + &key_exp_timestamp); + g_variant_get_child(variant, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXP_TIMESTAMP_PRIMARY, "x", + &key_exp_timestamp_primary); + + // create signature struct + Signature sig; + + sig.valid = valid; + sig.sigExpired = sigExpired; + sig.keyExpired = keyExpired; + sig.keyRevoked = keyRevoked; + sig.keyMissing = keyMissing; + sig.fingerprint = fingerprint; + sig.fingerprintPrimary = fingerprintPrimary; + sig.timestamp = Timepoint(std::chrono::seconds(timestamp)); + sig.expireTimestamp = Timepoint(std::chrono::seconds(exp_timestamp)); + sig.pubkeyAlgorithm = pubkey_algo; + sig.username = user_name; + sig.usermail = user_email; + sig.keyExpireTimestamp = Timepoint(std::chrono::seconds(key_exp_timestamp)); + sig.keyExpireTimestampPrimary = + Timepoint(std::chrono::seconds(key_exp_timestamp_primary)); + + commit.signatures.push_back(std::move(sig)); } + } - return commit; + return commit; +} + +// modified log_commit() from +// https://github.com/ostreedev/ostree/blob/main/src/ostree/ot-builtin-log.c#L40 +gboolean OSTreeRepo::parseCommitsRecursive(OstreeRepo* repo, + const gchar* checksum, + GError** error, + CommitList* commitList, + const std::string& branch, + gboolean isRecurse) { + GError* local_error{nullptr}; + + g_autoptr(GVariant) variant = nullptr; + if (!ostree_repo_load_variant(repo, OSTREE_OBJECT_TYPE_COMMIT, checksum, &variant, + &local_error)) { + return isRecurse && g_error_matches(local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); } - // modified log_commit() from https://github.com/ostreedev/ostree/blob/main/src/ostree/ot-builtin-log.c#L40 - gboolean OSTreeRepo::parseCommitsRecursive (OstreeRepo *repo, const gchar *checksum, GError **error, - CommitList *commitList, const std::string& branch, gboolean isRecurse) { - GError *local_error{nullptr}; + commitList->insert({static_cast(checksum), + parseCommit(variant, branch, static_cast(checksum))}); - g_autoptr (GVariant) variant = nullptr; - if (!ostree_repo_load_variant(repo, OSTREE_OBJECT_TYPE_COMMIT, checksum, &variant, &local_error)) { - return isRecurse && g_error_matches(local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); - } + // parent recursion + g_autofree char* parent = ostree_commit_get_parent(variant); - commitList->insert({ - static_cast(checksum), - parseCommit(variant, branch, static_cast(checksum)) - }); + return !(parent && !parseCommitsRecursive(repo, parent, error, commitList, branch, true)); +} - // parent recursion - g_autofree char *parent = ostree_commit_get_parent(variant); +CommitList OSTreeRepo::parseCommitsOfBranch(const std::string& branch) { + auto ret = CommitList(); - return !(parent && !parseCommitsRecursive(repo, parent, error, commitList, branch, true)); + // open repo + GError* error = nullptr; + OstreeRepo* repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); + if (repo == nullptr) { + g_printerr("Error opening repository: %s\n", error->message); + g_error_free(error); + return ret; } - CommitList OSTreeRepo::parseCommitsOfBranch(const std::string& branch) { - auto ret = CommitList(); + // recursive commit log + g_autofree char* checksum = nullptr; + if (!ostree_repo_resolve_rev(repo, branch.c_str(), false, &checksum, &error)) { + return ret; + } - // open repo - GError *error = nullptr; - OstreeRepo *repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); - if (repo == nullptr) { - g_printerr("Error opening repository: %s\n", error->message); - g_error_free(error); - return ret; - } + parseCommitsRecursive(repo, checksum, &error, &ret, branch); - // recursive commit log - g_autofree char *checksum = nullptr; - if (!ostree_repo_resolve_rev(repo, branch.c_str(), false, &checksum, &error)) { - return ret; - } + return ret; +} - parseCommitsRecursive(repo, checksum, &error, &ret, branch); +CommitList OSTreeRepo::parseCommitsAllBranches() { + std::istringstream branches_string(getBranchesAsString()); + std::string branch; - return ret; + CommitList commits_all_branches; + + while (branches_string >> branch) { + auto commits = parseCommitsOfBranch(branch); + commits_all_branches.insert(commits.begin(), commits.end()); } - CommitList OSTreeRepo::parseCommitsAllBranches() { + return commits_all_branches; +} - std::istringstream branches_string(getBranchesAsString()); - std::string branch; +std::string OSTreeRepo::getBranchesAsString() { + std::string branches_str; - CommitList commits_all_branches; + // open repo + GError* error{nullptr}; + OstreeRepo* repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); - while (branches_string >> branch) { - auto commits = parseCommitsOfBranch(branch); - commits_all_branches.insert(commits.begin(), commits.end()); - } - - return commits_all_branches; + if (repo == nullptr) { + g_printerr("Error opening repository: %s\n", error->message); + g_error_free(error); + return ""; } - std::string OSTreeRepo::getBranchesAsString() { - std::string branches_str; - - // open repo - GError *error {nullptr}; - OstreeRepo *repo = ostree_repo_open_at(AT_FDCWD, repoPath.c_str(), nullptr, &error); + // get a list of refs + GHashTable* refs_hash{nullptr}; + gboolean result = ostree_repo_list_refs_ext(repo, nullptr, &refs_hash, + OSTREE_REPO_LIST_REFS_EXT_NONE, nullptr, &error); + if (!result) { + g_printerr("Error listing refs: %s\n", error->message); + g_error_free(error); + g_object_unref(repo); + // TODO exit with error + return ""; + } - if (repo == nullptr) { - g_printerr("Error opening repository: %s\n", error->message); - g_error_free(error); - return ""; - } + // iterate through the refs + GHashTableIter iter; + gpointer key{nullptr}; + gpointer value{nullptr}; + g_hash_table_iter_init(&iter, refs_hash); + while (g_hash_table_iter_next(&iter, &key, &value)) { + const gchar* ref_name = static_cast(key); + branches_str += " " + std::string(ref_name); + } - // get a list of refs - GHashTable *refs_hash {nullptr}; - gboolean result = ostree_repo_list_refs_ext(repo, nullptr, &refs_hash, OSTREE_REPO_LIST_REFS_EXT_NONE, nullptr, &error); - if (!result) { - g_printerr("Error listing refs: %s\n", error->message); - g_error_free(error); - g_object_unref(repo); - // TODO exit with error - return ""; - } + // free + g_hash_table_unref(refs_hash); - // iterate through the refs - GHashTableIter iter; - gpointer key {nullptr}; - gpointer value {nullptr}; - g_hash_table_iter_init(&iter, refs_hash); - while (g_hash_table_iter_next(&iter, &key, &value)) { - const gchar *ref_name = static_cast(key); - branches_str += " " + std::string(ref_name); - } + return branches_str; +} - // free - g_hash_table_unref(refs_hash); +/// TODO This implementation should not rely on the ostree CLI -> change to libostree usage. +bool OSTreeRepo::promoteCommit(const std::string& hash, + const std::string& newRef, + const std::vector addMetadataStrings, + const std::string& newSubject, + bool keepMetadata) { + if (hash.size() <= 0 || newRef.size() <= 0) { + return false; + } - return branches_str; + std::string command = "ostree commit"; + command += " --repo=" + repoPath; + command += " -b " + newRef; + command += (newSubject.size() <= 0 ? "" : " -s \"" + newSubject + "\""); + command += (keepMetadata ? "" : " --keep-metadata"); + for (auto str : addMetadataStrings) { + command += " --add-metadata-string=\"" + str + "\""; } + command += " --tree=ref=" + hash; - /// TODO This implementation should not rely on the ostree CLI -> change to libostree usage. - bool OSTreeRepo::promoteCommit(const std::string& hash, const std::string& newRef, - const std::vector addMetadataStrings, - const std::string& newSubject, bool keepMetadata) { - if (hash.size() <= 0 || newRef.size() <= 0) { - return false; - } - - std::string command = "ostree commit"; - command += " --repo=" + repoPath; - command += " -b " + newRef; - command += (newSubject.size() <= 0 - ? "" - : " -s \"" + newSubject + "\""); - command += (keepMetadata - ? "" - : " --keep-metadata"); - for (auto str : addMetadataStrings) { - command += " --add-metadata-string=\"" + str + "\""; - } - command += " --tree=ref=" + hash; - - // run as CLI command - std::unique_ptr pipe(popen(command.c_str(), "r"), pclose); - - if (!pipe) { - return false; - } - return true; + // run as CLI command + std::unique_ptr pipe(popen(command.c_str(), "r"), pclose); + + if (!pipe) { + return false; } + return true; +} -} // namespace cpplibostree +} // namespace cpplibostree diff --git a/src/util/cpplibostree.hpp b/src/util/cpplibostree.hpp index ba1b3df..1210ea4 100644 --- a/src/util/cpplibostree.hpp +++ b/src/util/cpplibostree.hpp @@ -13,177 +13,183 @@ #pragma once // C++ +#include #include #include -#include #include #include // C +#include #include #include -#include // external #include #include - namespace cpplibostree { - using Clock = std::chrono::utc_clock; - using Timepoint = std::chrono::time_point; - - struct Signature { - bool valid {false}; - bool sigExpired{true}; - bool keyExpired{true}; - bool keyRevoked{false}; - bool keyMissing{true}; - std::string fingerprint; - std::string fingerprintPrimary; - Timepoint timestamp; - Timepoint expireTimestamp; - std::string pubkeyAlgorithm; - std::string username; - std::string usermail; - Timepoint keyExpireTimestamp; - Timepoint keyExpireTimestampPrimary; - } __attribute__((aligned(128))); - - struct Commit { - std::string hash; - std::string contentChecksum; - std::string subject{"OSTree TUI Error - invalid commit state"}; - std::string body; - std::string version; - Timepoint timestamp; - std::string parent; - std::string branch; - std::vector signatures; - } __attribute__((aligned(128))); - - // map commit hash to commit - using CommitList = std::unordered_map; +using Clock = std::chrono::utc_clock; +using Timepoint = std::chrono::time_point; + +struct Signature { + bool valid{false}; + bool sigExpired{true}; + bool keyExpired{true}; + bool keyRevoked{false}; + bool keyMissing{true}; + std::string fingerprint; + std::string fingerprintPrimary; + Timepoint timestamp; + Timepoint expireTimestamp; + std::string pubkeyAlgorithm; + std::string username; + std::string usermail; + Timepoint keyExpireTimestamp; + Timepoint keyExpireTimestampPrimary; +} __attribute__((aligned(128))); + +struct Commit { + std::string hash; + std::string contentChecksum; + std::string subject{"OSTree TUI Error - invalid commit state"}; + std::string body; + std::string version; + Timepoint timestamp; + std::string parent; + std::string branch; + std::vector signatures; +} __attribute__((aligned(128))); + +// map commit hash to commit +using CommitList = std::unordered_map; + +/** + * @brief OSTreeRepo functions as a C++ wrapper around libostree's OstreeRepo. + * The complete OSTree repository gets parsed into a complete commit list in + * commitList and a list of refs in branches. + */ +class OSTreeRepo { + private: + std::string repoPath; + CommitList commitList; + std::vector branches; + + public: + /** + * @brief Construct a new OSTreeRepo. + * + * @param repoPath Path to the OSTree Repository + */ + explicit OSTreeRepo(std::string repoPath); + + /** + * @brief Return a C-style pointer to a libostree OstreeRepo. This exists, to be + * able to access functions, that have not yet been adapted in this C++ wrapper. + * + * @return OstreeRepo* + */ + OstreeRepo* _c(); + + /// Getter + const std::string& getRepoPath() const; + /// Getter + const CommitList& getCommitList() const; + /// Getter + const std::vector& getBranches() const; + + // Methods + + /** + * @brief Reload the OSTree repository data. + * + * @return true if data was changed during the reload + * @return false if nothing changed + */ + bool updateData(); + + /** + * @brief Check if a certain commit is signed. This simply accesses the + * size() of commit.signatures. + * + * @param commit + * @return true if the commit is signed + * @return false if the commit is not signed + */ + static bool isCommitSigned(const Commit& commit); + + /** + * @brief Parse commits from a ostree log output to a commitList, mapping + * the hashes to commits. + * + * @param branch + * @return std::unordered_map + */ + CommitList parseCommitsOfBranch(const std::string& branch); + + /** + * @brief Performs parseCommitsOfBranch() on all available branches and + * merges all commit lists into one. + * + * @return std::unordered_map + */ + CommitList parseCommitsAllBranches(); + + // read & write access to OSTree repo: + + /** + * @brief Promotes a commit to another branch. Similar to: + * `ostree commit --repo=repo -b newRef -s newSubject --tree=ref=hash` + * + * @param hash hash of the commit to promote + * @param newRef branch to promote to + * @param addMetadataStrings list of metadata strings to add -> KEY=VALUE + * @param newSubject new commit subject, it needed + * @param keepMetadata should new commit keep metadata of old commit + * @return true on success + * @return false on failed promotion + */ + bool promoteCommit(const std::string& hash, + const std::string& newRef, + const std::vector addMetadataStrings, + const std::string& newSubject = "", + bool keepMetadata = true); + + private: + /** + * @brief Get all branches as a single string, separated by spaces. + * + * @return std::string All branch names, separated by spaces + */ + std::string getBranchesAsString(); + + /** + * @brief Parse a libostree GVariant commit to a C++ commit struct. + * + * @param variant pointer to GVariant commit + * @param branch branch of the commit + * @param hash commit hash + * @return Commit struct + */ + Commit parseCommit(GVariant* variant, const std::string& branch, const std::string& hash); /** - * @brief OSTreeRepo functions as a C++ wrapper around libostree's OstreeRepo. - * The complete OSTree repository gets parsed into a complete commit list in - * commitList and a list of refs in branches. + * @brief Parse all commits in a OstreeRepo into a commit vector. + * + * @param repo pointer to libostree Ostree repository + * @param checksum checksum of first commit + * @param error gets set, if an error occurred during parsing + * @param commitList commit list to parse the commits into + * @param branch branch to read the commit from + * @param isRecurse !Do not use!, or set to false. Used only for recursion. + * @return true if parsing was successful + * @return false if an error occurred during parsing */ - class OSTreeRepo { - private: - std::string repoPath; - CommitList commitList; - std::vector branches; - - public: - /** - * @brief Construct a new OSTreeRepo. - * - * @param repoPath Path to the OSTree Repository - */ - explicit OSTreeRepo(std::string repoPath); - - /** - * @brief Return a C-style pointer to a libostree OstreeRepo. This exists, to be - * able to access functions, that have not yet been adapted in this C++ wrapper. - * - * @return OstreeRepo* - */ - OstreeRepo* _c(); - - /// Getter - const std::string& getRepoPath() const; - /// Getter - const CommitList& getCommitList() const; - /// Getter - const std::vector& getBranches() const; - - // Methods - - /** - * @brief Reload the OSTree repository data. - * - * @return true if data was changed during the reload - * @return false if nothing changed - */ - bool updateData(); - - /** - * @brief Check if a certain commit is signed. This simply accesses the - * size() of commit.signatures. - * - * @param commit - * @return true if the commit is signed - * @return false if the commit is not signed - */ - static bool isCommitSigned(const Commit& commit); - - /** - * @brief Parse commits from a ostree log output to a commitList, mapping - * the hashes to commits. - * - * @param branch - * @return std::unordered_map - */ - CommitList parseCommitsOfBranch(const std::string& branch); - - /** - * @brief Performs parseCommitsOfBranch() on all available branches and - * merges all commit lists into one. - * - * @return std::unordered_map - */ - CommitList parseCommitsAllBranches(); - - // read & write access to OSTree repo: - - /** - * @brief Promotes a commit to another branch. Similar to: - * `ostree commit --repo=repo -b newRef -s newSubject --tree=ref=hash` - * - * @param hash hash of the commit to promote - * @param newRef branch to promote to - * @param addMetadataStrings list of metadata strings to add -> KEY=VALUE - * @param newSubject new commit subject, it needed - * @param keepMetadata should new commit keep metadata of old commit - * @return true on success - * @return false on failed promotion - */ - bool promoteCommit(const std::string& hash, const std::string& newRef, const std::vector addMetadataStrings, const std::string& newSubject = "", bool keepMetadata = true); - - private: - /** - * @brief Get all branches as a single string, separated by spaces. - * - * @return std::string All branch names, separated by spaces - */ - std::string getBranchesAsString(); - - /** - * @brief Parse a libostree GVariant commit to a C++ commit struct. - * - * @param variant pointer to GVariant commit - * @param branch branch of the commit - * @param hash commit hash - * @return Commit struct - */ - Commit parseCommit(GVariant *variant, const std::string& branch, const std::string& hash); - - /** - * @brief Parse all commits in a OstreeRepo into a commit vector. - * - * @param repo pointer to libostree Ostree repository - * @param checksum checksum of first commit - * @param error gets set, if an error occurred during parsing - * @param commitList commit list to parse the commits into - * @param branch branch to read the commit from - * @param isRecurse !Do not use!, or set to false. Used only for recursion. - * @return true if parsing was successful - * @return false if an error occurred during parsing - */ - gboolean parseCommitsRecursive (OstreeRepo *repo, const gchar *checksum, GError **error, - CommitList *commitList, const std::string& branch, - gboolean isRecurse = false); - }; - -} // namespace cpplibostree + gboolean parseCommitsRecursive(OstreeRepo* repo, + const gchar* checksum, + GError** error, + CommitList* commitList, + const std::string& branch, + gboolean isRecurse = false); +}; + +} // namespace cpplibostree From 948af8a293856f0f8e4251166a1ec48e5ce57ac9 Mon Sep 17 00:00:00 2001 From: Timon Ensel Date: Tue, 12 Nov 2024 22:25:31 +0100 Subject: [PATCH 5/5] Some code cleanup --- src/core/CMakeLists.txt | 3 +- src/core/OSTreeTUI.cpp | 111 ++++++++++++++++------------------- src/core/OSTreeTUI.hpp | 118 ++++++++++++++++++------------------- src/core/commit.cpp | 120 +++++++++++++++++--------------------- src/core/commit.hpp | 44 +++++++------- src/core/footer.cpp | 6 +- src/core/footer.hpp | 10 ++-- src/core/manager.cpp | 15 +++-- src/core/manager.hpp | 19 +++--- src/main.cpp | 2 +- src/util/cpplibostree.cpp | 4 +- src/util/cpplibostree.hpp | 2 +- 12 files changed, 218 insertions(+), 236 deletions(-) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 308483d..ca57dcb 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -9,8 +9,7 @@ add_library(ostree-tui_core commit.cpp manager.cpp manager.hpp OSTreeTUI.cpp - OSTreeTUI.hpp -) + OSTreeTUI.hpp) target_link_libraries(ostree-tui_core PRIVATE clip diff --git a/src/core/OSTreeTUI.cpp b/src/core/OSTreeTUI.cpp index 1b5c5ea..3ff9dee 100644 --- a/src/core/OSTreeTUI.cpp +++ b/src/core/OSTreeTUI.cpp @@ -20,7 +20,7 @@ #include "../util/cpplibostree.hpp" -OSTreeTUI::OSTreeTUI(const std::string& repo, const std::vector startupBranches) +OSTreeTUI::OSTreeTUI(const std::string& repo, const std::vector& startupBranches) : ostreeRepo(repo), selectedCommit(0), screen(ftxui::ScreenInteractive::Fullscreen()) { using namespace ftxui; @@ -38,18 +38,16 @@ OSTreeTUI::OSTreeTUI(const std::string& repo, const std::vector sta } } - // - UI ELEMENTS ---------- ---------- - // COMMIT TREE - refresh_commitComponents(); + RefreshCommitComponents(); tree = Renderer([&] { - refresh_commitComponents(); + RefreshCommitComponents(); selectedCommit = std::min(selectedCommit, visibleCommitViewMap.size() - 1); - // TODO check for promotion & pass information if needed + // check for promotion & gray-out branch-colors if needed if (inPromotionSelection && promotionBranch.size() != 0) { std::unordered_map promotionBranchColorMap{}; - for (auto& [str, col] : branchColorMap) { + for (const auto& [str, col] : branchColorMap) { if (str == promotionBranch) { promotionBranchColorMap.insert({str, col}); } else { @@ -74,7 +72,7 @@ OSTreeTUI::OSTreeTUI(const std::string& repo, const std::vector sta } if ((!inPromotionSelection && event == Event::ArrowDown) || (event.is_mouse() && event.mouse().button == Mouse::WheelDown)) { - selectedCommit = std::min(selectedCommit + 1, getVisibleCommitViewMap().size() - 1); + selectedCommit = std::min(selectedCommit + 1, GetVisibleCommitViewMap().size() - 1); adjustScrollToSelectedCommit(); return true; } @@ -84,7 +82,7 @@ OSTreeTUI::OSTreeTUI(const std::string& repo, const std::vector sta // INTERCHANGEABLE VIEW // info infoView = Renderer([&] { - visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); + parseVisibleCommitMap(); if (visibleCommitViewMap.size() <= 0) { return text(" no commit info available ") | color(Color::RedLight) | bold | center; } @@ -94,34 +92,28 @@ OSTreeTUI::OSTreeTUI(const std::string& repo, const std::vector sta // filter filterManager = - std::unique_ptr(new BranchBoxManager(ostreeRepo, visibleBranches)); + std::unique_ptr(new BranchBoxManager(*this, ostreeRepo, visibleBranches)); filterView = Renderer(filterManager->branchBoxes, [&] { return filterManager->branchBoxRender(); }); - filterView = CatchEvent(filterView, [&](Event event) { - if (event.is_mouse() && event.mouse().button == Mouse::Button::Left) { - refresh_commitListComoponent(); - } - return false; - }); // interchangeable view (composed) manager = std::unique_ptr(new Manager(*this, infoView, filterView)); managerRenderer = manager->getManagerRenderer(); // FOOTER - footerRenderer = Renderer([&] { return footer.footerRender(); }); + FooterRenderer = Renderer([&] { return footer.FooterRender(); }); // BUILD MAIN CONTAINER container = Component(managerRenderer); container = ResizableSplitLeft(commitListComponent, container, &logSize); - container = ResizableSplitBottom(footerRenderer, container, &footerSize); + container = ResizableSplitBottom(FooterRenderer, container, &footerSize); commitListComponent->TakeFocus(); // add application shortcuts mainContainer = CatchEvent(container | border, [&](const Event& event) { if (event == Event::AltP) { - setPromotionMode(true, visibleCommitViewMap.at(selectedCommit)); + SetPromotionMode(true, visibleCommitViewMap.at(selectedCommit)); } // copy commit id if (event == Event::AltC) { @@ -132,7 +124,7 @@ OSTreeTUI::OSTreeTUI(const std::string& repo, const std::vector sta } // refresh repository if (event == Event::AltR) { - refresh_repository(); + RefreshOSTreeRepository(); notificationText = " Refreshed Repository Data "; return true; } @@ -151,7 +143,7 @@ OSTreeTUI::OSTreeTUI(const std::string& repo, const std::vector sta }); } -int OSTreeTUI::run() { +int OSTreeTUI::Run() { using namespace ftxui; // footer notification update loader // Probably not the best solution, having an active wait and should maybe @@ -162,12 +154,12 @@ int OSTreeTUI::run() { using namespace std::chrono_literals; // notification is set if (notificationText != "") { - footer.setContent(notificationText); + footer.SetContent(notificationText); screen.Post(Event::Custom); std::this_thread::sleep_for(2s); // clear notification notificationText = ""; - footer.resetContent(); + footer.ResetContent(); screen.Post(Event::Custom); } std::this_thread::sleep_for(0.2s); @@ -181,12 +173,12 @@ int OSTreeTUI::run() { return EXIT_SUCCESS; } -void OSTreeTUI::refresh_commitComponents() { +void OSTreeTUI::RefreshCommitComponents() { using namespace ftxui; commitComponents.clear(); int i{0}; - visibleCommitViewMap = parseVisibleCommitMap(ostreeRepo, visibleBranches); + parseVisibleCommitMap(); for (auto& hash : visibleCommitViewMap) { commitComponents.push_back(CommitRender::CommitComponent(i, hash, *this)); i++; @@ -198,22 +190,24 @@ void OSTreeTUI::refresh_commitComponents() { : Container::Stacked(commitComponents); } -void OSTreeTUI::refresh_commitListComoponent() { +void OSTreeTUI::RefreshCommitListComponent() { using namespace ftxui; + parseVisibleCommitMap(); + commitListComponent->DetachAllChildren(); - refresh_commitComponents(); + RefreshCommitComponents(); Component tmp = Container::Horizontal({tree, commitList}); commitListComponent->Add(tmp); } -bool OSTreeTUI::refresh_repository() { +bool OSTreeTUI::RefreshOSTreeRepository() { ostreeRepo.updateData(); - refresh_commitListComoponent(); + RefreshCommitListComponent(); return true; } -bool OSTreeTUI::setPromotionMode(bool active, std::string hash, bool setPromotionBranch) { +bool OSTreeTUI::SetPromotionMode(bool active, const std::string& hash, bool SetPromotionBranch) { // deactivate promotion mode if (!active) { inPromotionSelection = false; @@ -224,7 +218,7 @@ bool OSTreeTUI::setPromotionMode(bool active, std::string hash, bool setPromotio // set promotion mode if (!inPromotionSelection || hash != promotionHash) { inPromotionSelection = true; - if (setPromotionBranch) { + if (SetPromotionBranch) { promotionBranch = promotionBranch.empty() ? columnToBranchMap.at(0) : promotionBranch; } promotionHash = hash; @@ -234,14 +228,14 @@ bool OSTreeTUI::setPromotionMode(bool active, std::string hash, bool setPromotio return false; } -bool OSTreeTUI::promoteCommit(std::string hash, - std::string branch, - std::vector metadataStrings, - std::string newSubject, +bool OSTreeTUI::PromoteCommit(const std::string& hash, + const std::string& targetBranch, + const std::vector& metadataStrings, + const std::string& newSubject, bool keepMetadata) { bool success = - ostreeRepo.promoteCommit(hash, branch, metadataStrings, newSubject, keepMetadata); - setPromotionMode(false); + ostreeRepo.PromoteCommit(hash, targetBranch, metadataStrings, newSubject, keepMetadata); + SetPromotionMode(false); // reload repository if (success) { scrollOffset = 0; @@ -251,13 +245,10 @@ bool OSTreeTUI::promoteCommit(std::string hash, return success; } -std::vector OSTreeTUI::parseVisibleCommitMap( - cpplibostree::OSTreeRepo& repo, - std::unordered_map& visibleBranches) { - std::vector visibleCommitViewMap{}; +void OSTreeTUI::parseVisibleCommitMap() { // get filtered commits visibleCommitViewMap = {}; - for (const auto& commitPair : repo.getCommitList()) { + for (const auto& commitPair : ostreeRepo.getCommitList()) { if (visibleBranches[commitPair.second.branch]) { visibleCommitViewMap.push_back(commitPair.first); } @@ -265,11 +256,9 @@ std::vector OSTreeTUI::parseVisibleCommitMap( // sort by date std::sort(visibleCommitViewMap.begin(), visibleCommitViewMap.end(), [&](const std::string& a, const std::string& b) { - return repo.getCommitList().at(a).timestamp > - repo.getCommitList().at(b).timestamp; + return ostreeRepo.getCommitList().at(a).timestamp > + ostreeRepo.getCommitList().at(b).timestamp; }); - - return visibleCommitViewMap; } void OSTreeTUI::adjustScrollToSelectedCommit() { @@ -287,61 +276,61 @@ void OSTreeTUI::adjustScrollToSelectedCommit() { } // SETTER & non-const GETTER -void OSTreeTUI::setPromotionBranch(std::string promotionBranch) { +void OSTreeTUI::SetPromotionBranch(const std::string& promotionBranch) { this->promotionBranch = promotionBranch; } -void OSTreeTUI::setSelectedCommit(size_t selectedCommit) { +void OSTreeTUI::SetSelectedCommit(size_t selectedCommit) { this->selectedCommit = selectedCommit; adjustScrollToSelectedCommit(); } -std::vector& OSTreeTUI::getColumnToBranchMap() { +std::vector& OSTreeTUI::GetColumnToBranchMap() { return columnToBranchMap; } -ftxui::ScreenInteractive& OSTreeTUI::getScreen() { +ftxui::ScreenInteractive& OSTreeTUI::GetScreen() { return screen; } // GETTER -const cpplibostree::OSTreeRepo& OSTreeTUI::getOstreeRepo() const { +const cpplibostree::OSTreeRepo& OSTreeTUI::GetOstreeRepo() const { return ostreeRepo; } -const size_t& OSTreeTUI::getSelectedCommit() const { +const size_t& OSTreeTUI::GetSelectedCommit() const { return selectedCommit; } -const std::string& OSTreeTUI::getPromotionBranch() const { +const std::string& OSTreeTUI::GetPromotionBranch() const { return promotionBranch; } -const std::unordered_map& OSTreeTUI::getVisibleBranches() const { +const std::unordered_map& OSTreeTUI::GetVisibleBranches() const { return visibleBranches; } -const std::vector& OSTreeTUI::getColumnToBranchMap() const { +const std::vector& OSTreeTUI::GetColumnToBranchMap() const { return columnToBranchMap; } -const std::vector& OSTreeTUI::getVisibleCommitViewMap() const { +const std::vector& OSTreeTUI::GetVisibleCommitViewMap() const { return visibleCommitViewMap; } -const std::unordered_map& OSTreeTUI::getBranchColorMap() const { +const std::unordered_map& OSTreeTUI::GetBranchColorMap() const { return branchColorMap; } -const int& OSTreeTUI::getScrollOffset() const { +int OSTreeTUI::GetScrollOffset() const { return scrollOffset; } -const bool& OSTreeTUI::getInPromotionSelection() const { +bool OSTreeTUI::GetInPromotionSelection() const { return inPromotionSelection; } -const std::string& OSTreeTUI::getPromotionHash() const { +const std::string& OSTreeTUI::GetPromotionHash() const { return promotionHash; } @@ -393,7 +382,7 @@ int OSTreeTUI::showHelp(const std::string& caller, const std::string& errorMessa int OSTreeTUI::showVersion() { using namespace ftxui; - auto versionText = text("ostree-tui 0.2.1"); + auto versionText = text("ostree-tui 0.3.0"); auto screen = Screen::Create(Dimension::Fit(versionText)); Render(screen, versionText); diff --git a/src/core/OSTreeTUI.hpp b/src/core/OSTreeTUI.hpp index a673f34..14ce501 100644 --- a/src/core/OSTreeTUI.hpp +++ b/src/core/OSTreeTUI.hpp @@ -25,85 +25,86 @@ class OSTreeTUI { /** * @brief Constructs, builds and assembles all components of the OSTreeTUI. * - * @param repo ostree repository (OSTreeRepo) - * @param startupBranches optional list of branches to pre-select at startup (providing nothing - * will display all branches) + * @param repo Path to the OSTree repository directory. + * @param startupBranches Optional list of branches to pre-select at startup (providing nothing + * will display all branches). */ explicit OSTreeTUI(const std::string& repo, - const std::vector startupBranches = {}); + const std::vector& startupBranches = {}); /** * @brief Runs the OSTreeTUI (starts the ftxui screen loop). * - * @return exit code + * @return Exit Code */ - int run(); + int Run(); - /// @brief Refresh Level 3: Refreshes the commit components - void refresh_commitComponents(); + /// @brief OSTreeTUI Refresh Level 3: Refreshes the commit components. + void RefreshCommitComponents(); - /// @brief Refresh Level 2: Refreshes the commit list component & upper levels - void refresh_commitListComoponent(); + /// @brief OSTreeTUI Refresh Level 2: Refreshes the commit list component & upper levels. + void RefreshCommitListComponent(); - /// @brief Refresh Level 1: Refreshes complete repository & upper levels - bool refresh_repository(); + /// @brief OSTreeTUI Refresh Level 1: Refreshes complete repository & upper levels. + bool RefreshOSTreeRepository(); /** - * @brief sets the promotion mode - * @param active activate (true), or deactivate (false) promotion mode - * @param hash provide if setting mode to true + * @brief Sets the promotion mode: Defines if the ostree-tui currently displays a commit + * promotion window. + * + * @param active Activate (true), or deactivate (false) the promotion mode. + * @param hash Must only be provided, if `active` is set to true. + * @param SetPromotionBranch If `active` is true, this defines, if the promotino Branch should + * be reset to the first visible branch. * @return false, if other promotion gets overwritten */ - bool setPromotionMode(bool active, std::string hash = "", bool setPromotionBranch = true); + bool SetPromotionMode(bool active, + const std::string& hash = "", + bool SetPromotionBranch = true); - /** @brief promote a commit - * @param hash - * @param branch + /** + * @brief Promotes a commit, by passing it to the cpplibostree and refreshing the UI. + * + * @param hash Hash of commit to be promoted. + * @param targetBranch Branch to promote the commit to. + * @param metadataStrings Optional additional metadata-strings to be set. + * @param newSubject New commit subject. + * @param keepMetadata Keep metadata of old commit. * @return promotion success */ - bool promoteCommit(std::string hash, - std::string branch, - std::vector metadataStrings = {}, - std::string newSubject = "", + bool PromoteCommit(const std::string& hash, + const std::string& targetBranch, + const std::vector& metadataStrings = {}, + const std::string& newSubject = "", bool keepMetadata = true); private: - /** - * @brief Calculates all visible commits from an OSTreeRepo and a list of branches. - * - * @param repo OSTreeRepo - * @param visibleBranches Map: branch name -> visible - * @return Complete list of commit hashes in repo, that are part of the given branches - */ - std::vector parseVisibleCommitMap( - cpplibostree::OSTreeRepo& repo, - std::unordered_map& visibleBranches); + /// @brief Calculates all visible commits from an OSTreeRepo and a list of branches. + void parseVisibleCommitMap(); - /** - * @brief Adjust scroll offset to fit the selected commit - */ + /// @brief Adjust scroll offset to fit the selected commit. void adjustScrollToSelectedCommit(); public: // SETTER - void setPromotionBranch(std::string promotionBranch); - void setSelectedCommit(size_t selectedCommit); + void SetPromotionBranch(const std::string& promotionBranch); + void SetSelectedCommit(size_t selectedCommit); // non-const GETTER - std::vector& getColumnToBranchMap(); - ftxui::ScreenInteractive& getScreen(); + [[nodiscard]] std::vector& GetColumnToBranchMap(); + [[nodiscard]] ftxui::ScreenInteractive& GetScreen(); // GETTER - const cpplibostree::OSTreeRepo& getOstreeRepo() const; - const size_t& getSelectedCommit() const; - const std::string& getPromotionBranch() const; - const std::unordered_map& getVisibleBranches() const; - const std::vector& getColumnToBranchMap() const; - const std::vector& getVisibleCommitViewMap() const; - const std::unordered_map& getBranchColorMap() const; - const int& getScrollOffset() const; - const bool& getInPromotionSelection() const; - const std::string& getPromotionHash() const; + [[nodiscard]] const cpplibostree::OSTreeRepo& GetOstreeRepo() const; + [[nodiscard]] const size_t& GetSelectedCommit() const; + [[nodiscard]] const std::string& GetPromotionBranch() const; + [[nodiscard]] const std::unordered_map& GetVisibleBranches() const; + [[nodiscard]] const std::vector& GetColumnToBranchMap() const; + [[nodiscard]] const std::vector& GetVisibleCommitViewMap() const; + [[nodiscard]] const std::unordered_map& GetBranchColorMap() const; + [[nodiscard]] int GetScrollOffset() const; + [[nodiscard]] bool GetInPromotionSelection() const; + [[nodiscard]] const std::string& GetPromotionHash() const; private: // model @@ -112,10 +113,8 @@ class OSTreeTUI { // backend states size_t selectedCommit; std::unordered_map visibleBranches; // map branch -> visibe - std::vector - columnToBranchMap; // map branch -> column in commit-tree (may be merged into one - // data-structure with visibleBranches) - std::vector visibleCommitViewMap; // map view-index -> commit-hash + std::vector columnToBranchMap; // map branch -> column in commit-tree + std::vector visibleCommitViewMap; // map view-index -> commit-hash std::unordered_map branchColorMap; // map branch -> color std::string notificationText; // footer notification @@ -142,24 +141,23 @@ class OSTreeTUI { ftxui::Component infoView; ftxui::Component filterView; ftxui::Component managerRenderer; - ftxui::Component footerRenderer; + ftxui::Component FooterRenderer; ftxui::Component container; public: /** - * @brief Print help page + * @brief Print a help page including usage, options, etc. * * @param caller argv[0] - * @param errorMessage optional error message to print on top - * @return 0, if no error message provided - * @return 1, if error message is provided, assuming bad program stop + * @param errorMessage Optional error message to print on top. + * @return Exit Code */ static int showHelp(const std::string& caller, const std::string& errorMessage = ""); /** * @brief Print the application version * - * @return int + * @return Exit Code */ static int showVersion(); }; diff --git a/src/core/commit.cpp b/src/core/commit.cpp index 1910d45..e514ada 100644 --- a/src/core/commit.cpp +++ b/src/core/commit.cpp @@ -57,7 +57,7 @@ Element DefaultRenderState(const WindowRenderState& state, if (!dimmable) { selectedColor = Color::White; } - if (selectedColor == Color::White & dimmable) { + if (selectedColor == Color::White && dimmable) { element |= dim; } else { element |= bold; @@ -79,17 +79,17 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { : commitPosition(position), hash(std::move(commit)), ostreetui(ostreetui), - commit(ostreetui.getOstreeRepo().getCommitList().at(hash)), + commit(ostreetui.GetOstreeRepo().getCommitList().at(hash)), newVersion(this->commit.version), drag_initial_y(position * COMMIT_WINDOW_HEIGHT), drag_initial_x(1) { inner = Renderer([&] { return vbox({ - text(ostreetui.getOstreeRepo().getCommitList().at(hash).subject), + text(ostreetui.GetOstreeRepo().getCommitList().at(hash).subject), text( std::format("{:%Y-%m-%d %T %Ez}", std::chrono::time_point_cast( - ostreetui.getOstreeRepo().getCommitList().at(hash).timestamp))), + ostreetui.GetOstreeRepo().getCommitList().at(hash).timestamp))), }); }); simpleCommit = inner; @@ -127,11 +127,11 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { TakeFocus(); // manually fix focus issues // change to proper control layout if possible... - ostreetui.getScreen().Post(Event::ArrowDown); - ostreetui.getScreen().Post(Event::ArrowRight); - ostreetui.getScreen().Post(Event::ArrowDown); - ostreetui.getScreen().Post(Event::ArrowRight); - ostreetui.getScreen().Post(Event::ArrowUp); + ostreetui.GetScreen().Post(Event::ArrowDown); + ostreetui.GetScreen().Post(Event::ArrowRight); + ostreetui.GetScreen().Post(Event::ArrowDown); + ostreetui.GetScreen().Post(Event::ArrowRight); + ostreetui.GetScreen().Post(Event::ArrowUp); } } @@ -141,20 +141,20 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { if (!newVersion.empty()) { metadataStrings.push_back("version=" + newVersion); } - ostreetui.promoteCommit(hash, ostreetui.getPromotionBranch(), metadataStrings, newSubject, + ostreetui.PromoteCommit(hash, ostreetui.GetPromotionBranch(), metadataStrings, newSubject, true); resetWindow(); } void cancelPromotion() { - ostreetui.setPromotionMode(false); + ostreetui.SetPromotionMode(false); resetWindow(); } Element Render() final { // check if promotion was started not from drag & drop, but from ostreetui - if (ostreetui.getInPromotionSelection() && ostreetui.getPromotionHash() == hash) { - if (!ostreetui.getPromotionBranch().empty()) { + if (ostreetui.GetInPromotionSelection() && ostreetui.GetPromotionHash() == hash) { + if (!ostreetui.GetPromotionBranch().empty()) { startPromotionWindow(); } else { resetWindow(false); @@ -165,20 +165,20 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { const WindowRenderState state = {element, title(), Active(), drag_}; - if (commitPosition == ostreetui.getSelectedCommit()) { // selected & not in promotion + if (commitPosition == ostreetui.GetSelectedCommit()) { // selected & not in promotion element = render ? render(state) - : DefaultRenderState(state, ostreetui.getBranchColorMap().at(commit.branch), - ostreetui.getPromotionHash() != hash); + : DefaultRenderState(state, ostreetui.GetBranchColorMap().at(commit.branch), + ostreetui.GetPromotionHash() != hash); } else { element = render ? render(state) : DefaultRenderState(state, Color::White, - ostreetui.getPromotionHash() != hash); + ostreetui.GetPromotionHash() != hash); } // Position and record the drawn area of the window. element |= reflect(box_window_); - element |= PositionAndSize(left(), top() + ostreetui.getScrollOffset(), width(), height()); + element |= PositionAndSize(left(), top() + ostreetui.GetScrollOffset(), width(), height()); element |= reflect(box_); return element; @@ -189,29 +189,24 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { return true; } - if (ostreetui.getInPromotionSelection()) { + if (ostreetui.GetInPromotionSelection()) { // navigate promotion branches if (event == Event::ArrowLeft) { - const long int it = std::find(ostreetui.getColumnToBranchMap().begin(), - ostreetui.getColumnToBranchMap().end(), - ostreetui.getPromotionBranch()) - - ostreetui.getColumnToBranchMap().begin(); - ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at( - (it - 1) % ostreetui.getColumnToBranchMap().size())); + const long int it = std::find(ostreetui.GetColumnToBranchMap().begin(), + ostreetui.GetColumnToBranchMap().end(), + ostreetui.GetPromotionBranch()) - + ostreetui.GetColumnToBranchMap().begin(); + ostreetui.SetPromotionBranch(ostreetui.GetColumnToBranchMap().at( + (it - 1) % ostreetui.GetColumnToBranchMap().size())); return true; } if (event == Event::ArrowRight) { - const long int it = std::find(ostreetui.getColumnToBranchMap().begin(), - ostreetui.getColumnToBranchMap().end(), - ostreetui.getPromotionBranch()) - - ostreetui.getColumnToBranchMap().begin(); - ostreetui.setPromotionBranch(ostreetui.getColumnToBranchMap().at( - (it + 1) % ostreetui.getColumnToBranchMap().size())); - return true; - } - // promote - if (event == Event::Return) { - executePromotion(); + const long int it = std::find(ostreetui.GetColumnToBranchMap().begin(), + ostreetui.GetColumnToBranchMap().end(), + ostreetui.GetPromotionBranch()) - + ostreetui.GetColumnToBranchMap().begin(); + ostreetui.SetPromotionBranch(ostreetui.GetColumnToBranchMap().at( + (it + 1) % ostreetui.GetColumnToBranchMap().size())); return true; } // cancel @@ -225,29 +220,26 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { return false; } - if (ostreetui.getInPromotionSelection() && !drag_) { + if (ostreetui.GetInPromotionSelection() && !drag_) { return true; } - if (event.mouse().button == Mouse::Left) { - // update ostreetui - ostreetui.setSelectedCommit(commitPosition); - } - mouse_hover_ = box_window_.Contain(event.mouse().x, event.mouse().y); - // potentially indicate mouse hover - // if (box_window_.Contain(event.mouse().x, event.mouse().y)) {} + + if (mouse_hover_ && event.mouse().button == Mouse::Left) { + ostreetui.SetSelectedCommit(commitPosition); + } if (captured_mouse_) { if (event.mouse().motion == Mouse::Released) { // reset mouse captured_mouse_ = nullptr; // check if position matches branch & do something if it does - if (ostreetui.getPromotionBranch().empty()) { - ostreetui.setPromotionMode(false, hash); + if (ostreetui.GetPromotionBranch().empty()) { + ostreetui.SetPromotionMode(false, hash); resetWindow(); } else { - ostreetui.setPromotionMode(true, hash); + ostreetui.SetPromotionMode(true, hash); } return true; } @@ -256,24 +248,24 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { left() = event.mouse().x - drag_start_x - box_.x_min; top() = event.mouse().y - drag_start_y - box_.y_min; // potential promotion - ostreetui.setPromotionMode(true, hash, false); + ostreetui.SetPromotionMode(true, hash, false); // calculate which branch currently is hovered over - ostreetui.setPromotionBranch(""); + ostreetui.SetPromotionBranch(""); const int branch_pos = event.mouse().x / 2; int count{0}; - for (const auto& [branch, visible] : ostreetui.getVisibleBranches()) { + for (const auto& [branch, visible] : ostreetui.GetVisibleBranches()) { if (visible) { ++count; } if (count == branch_pos) { - ostreetui.setPromotionBranch( - ostreetui.getColumnToBranchMap().at(event.mouse().x / 2 - 1)); + ostreetui.SetPromotionBranch( + ostreetui.GetColumnToBranchMap().at(event.mouse().x / 2 - 1)); break; } } } else { // not promotion - ostreetui.setPromotionMode(false); + ostreetui.SetPromotionMode(false); } // Clamp the window size. @@ -361,7 +353,7 @@ class CommitComponentImpl : public ComponentBase, public WindowOptions { Input(&newVersion, commit.version) | underlined}), Renderer([&] { return vbox({text(" ┆"), text(" ┆ to branch:"), - text(" ☐ " + ostreetui.getPromotionBranch()) | bold, text(" │") | bold}); + text(" ☐ " + ostreetui.GetPromotionBranch()) | bold, text(" │") | bold}); }), Container::Horizontal({ Button(" Cancel ", [&] { cancelPromotion(); }) | color(Color::Red) | flex, @@ -379,16 +371,16 @@ ftxui::Element commitRender(OSTreeTUI& ostreetui, const std::unordered_map& branchColorMap) { using namespace ftxui; - int scrollOffset = ostreetui.getScrollOffset(); + int scrollOffset = ostreetui.GetScrollOffset(); // check empty commit list - if (ostreetui.getVisibleCommitViewMap().empty() || ostreetui.getVisibleBranches().empty()) { + if (ostreetui.GetVisibleCommitViewMap().empty() || ostreetui.GetVisibleBranches().empty()) { return color(Color::RedLight, text(" no commits to be shown ") | bold | center); } // stores the dedicated tree-column of each branch, -1 meaning not displayed yet std::unordered_map usedBranches{}; - for (const auto& branchPair : ostreetui.getVisibleBranches()) { + for (const auto& branchPair : ostreetui.GetVisibleBranches()) { if (branchPair.second) { usedBranches[branchPair.first] = -1; } @@ -396,18 +388,16 @@ ftxui::Element commitRender(OSTreeTUI& ostreetui, int nextAvailableSpace = static_cast(usedBranches.size() - 1); // - RENDER - - // left tree, right commits Elements treeElements{}; - Elements commElements{}; - ostreetui.getColumnToBranchMap().clear(); - for (const auto& visibleCommitIndex : ostreetui.getVisibleCommitViewMap()) { + ostreetui.GetColumnToBranchMap().clear(); + for (const auto& visibleCommitIndex : ostreetui.GetVisibleCommitViewMap()) { const cpplibostree::Commit commit = - ostreetui.getOstreeRepo().getCommitList().at(visibleCommitIndex); + ostreetui.GetOstreeRepo().getCommitList().at(visibleCommitIndex); // branch head if it is first branch usage const std::string relevantBranch = commit.branch; if (usedBranches.at(relevantBranch) == -1) { - ostreetui.getColumnToBranchMap().push_back(relevantBranch); + ostreetui.GetColumnToBranchMap().push_back(relevantBranch); usedBranches.at(relevantBranch) = nextAvailableSpace--; } // commit @@ -422,9 +412,9 @@ ftxui::Element commitRender(OSTreeTUI& ostreetui, } } } - std::reverse(ostreetui.getColumnToBranchMap().begin(), ostreetui.getColumnToBranchMap().end()); + std::reverse(ostreetui.GetColumnToBranchMap().begin(), ostreetui.GetColumnToBranchMap().end()); - return hbox({vbox(std::move(treeElements)), vbox(std::move(commElements))}); + return vbox(std::move(treeElements)); } ftxui::Element addTreeLine(const RenderTree& treeLineType, diff --git a/src/core/commit.hpp b/src/core/commit.hpp index 0f0c18e..3825900 100644 --- a/src/core/commit.hpp +++ b/src/core/commit.hpp @@ -45,35 +45,39 @@ enum RenderTree : uint8_t { * after being dragged & let go. When hovering over a branch, * defined in `columnToBranchMap`, the window expands to a commit * promotion window. - * To be used with other windows, use a ftxui::Component::Stacked. + * To be used with other windows, use a `ftxui::Component::Stacked`. * - * @return Component + * @return UI Component */ -ftxui::Component CommitComponent(int position, const std::string& commit, OSTreeTUI& ostreetui); +[[nodiscard]] ftxui::Component CommitComponent(int position, + const std::string& commit, + OSTreeTUI& ostreetui); /** - * @brief create a Renderer for the commit section + * @brief Creates a Renderer for the commit section. * - * @param ostreetui OSTreeTUI containing OSTreeRepo and UI info - * @param branchColorMap Map from branch to its display color - * @param selectedCommit Commit that should be marked as selected - * @return ftxui::Element + * @param ostreetui OSTreeTUI containing OSTreeRepo and UI info. + * @param branchColorMap Map from branch to its display color. + * @param selectedCommit Commit that should be marked as selected. + * @return UI Element */ -ftxui::Element commitRender(OSTreeTUI& ostreetui, - const std::unordered_map& branchColorMap); +[[nodiscard]] ftxui::Element commitRender( + OSTreeTUI& ostreetui, + const std::unordered_map& branchColorMap); /** - * @brief build a commit-tree line + * @brief Builds a commit-tree line. * - * @param treeLineType type of commit-tree - * @param commit commit to render / get info from - * @param usedBranches branches to render - * @param branchColorMap branch colors - * @return ftxui::Element commit-tree line + * @param treeLineType Type of commit-tree. + * @param commit Commit to get the information from (to render). + * @param usedBranches Branches, that should be rendered (visible). + * @param branchColorMap Branch colors to use. + * @return UI Element, one commit-tree line. */ -ftxui::Element addTreeLine(const RenderTree& treeLineType, - const cpplibostree::Commit& commit, - const std::unordered_map& usedBranches, - const std::unordered_map& branchColorMap); +[[nodiscard]] ftxui::Element addTreeLine( + const RenderTree& treeLineType, + const cpplibostree::Commit& commit, + const std::unordered_map& usedBranches, + const std::unordered_map& branchColorMap); } // namespace CommitRender diff --git a/src/core/footer.cpp b/src/core/footer.cpp index 8faf514..6791033 100644 --- a/src/core/footer.cpp +++ b/src/core/footer.cpp @@ -2,7 +2,7 @@ #include "footer.hpp" -ftxui::Element Footer::footerRender() { +ftxui::Element Footer::FooterRender() { using namespace ftxui; return hbox({ @@ -13,10 +13,10 @@ ftxui::Element Footer::footerRender() { }); } -void Footer::resetContent() { +void Footer::ResetContent() { content = DEFAULT_CONTENT; } -void Footer::setContent(std::string content) { +void Footer::SetContent(std::string content) { this->content = content; } diff --git a/src/core/footer.hpp b/src/core/footer.hpp index de3cf89..899faed 100644 --- a/src/core/footer.hpp +++ b/src/core/footer.hpp @@ -8,14 +8,14 @@ class Footer { public: Footer() = default; - /// reset footer text to default string - void resetContent(); + /// @brief Resets footer text to default string. + void ResetContent(); - /// create a Renderer for the footer section - ftxui::Element footerRender(); + /// @brief Creates a Renderer for the footer section. + ftxui::Element FooterRender(); // Setter - void setContent(std::string content); + void SetContent(std::string content); private: const std::string DEFAULT_CONTENT{ diff --git a/src/core/manager.cpp b/src/core/manager.cpp index 876ed40..36faca3 100644 --- a/src/core/manager.cpp +++ b/src/core/manager.cpp @@ -29,10 +29,10 @@ Manager::Manager(OSTreeTUI& ostreetui, Renderer([] { return vbox({filler()}) | flex; }), // push elements apart Renderer([&] { Elements branches; - for (size_t i{ostreetui.getColumnToBranchMap().size()}; i > 0; i--) { - std::string branch = ostreetui.getColumnToBranchMap().at(i - 1); + for (size_t i{ostreetui.GetColumnToBranchMap().size()}; i > 0; i--) { + std::string branch = ostreetui.GetColumnToBranchMap().at(i - 1); std::string line = "――☐――― " + branch; - branches.push_back(text(line) | color(ostreetui.getBranchColorMap().at(branch))); + branches.push_back(text(line) | color(ostreetui.GetBranchColorMap().at(branch))); } return vbox(branches); })}); @@ -42,19 +42,22 @@ ftxui::Component Manager::getManagerRenderer() { return managerRenderer; } -const int& Manager::getTabIndex() const { +int Manager::getTabIndex() const { return tab_index; } // BranchBoxManager -BranchBoxManager::BranchBoxManager(cpplibostree::OSTreeRepo& repo, +BranchBoxManager::BranchBoxManager(OSTreeTUI& ostreetui, + cpplibostree::OSTreeRepo& repo, std::unordered_map& visibleBranches) { using namespace ftxui; + CheckboxOption cboption = {.on_change = [&] { ostreetui.RefreshCommitListComponent(); }}; + // branch visibility for (const auto& branch : repo.getBranches()) { - branchBoxes->Add(Checkbox(branch, &(visibleBranches.at(branch)))); + branchBoxes->Add(Checkbox(branch, &(visibleBranches.at(branch)), cboption)); } } diff --git a/src/core/manager.hpp b/src/core/manager.hpp index 8719e8c..12ac40d 100644 --- a/src/core/manager.hpp +++ b/src/core/manager.hpp @@ -1,9 +1,7 @@ /*_____________________________________________________________ | Manager Render | Right portion of main window, includes branch filter & - | detailed commit info of the selected commit. In future - | different modes should be supported (like a rebase mode - | exchangeable with the commit info) + | detailed commit info of the selected commit. |___________________________________________________________*/ #pragma once @@ -37,7 +35,7 @@ class Manager { public: ftxui::Component getManagerRenderer(); - const int& getTabIndex() const; + int getTabIndex() const; }; class CommitInfoManager { @@ -48,15 +46,13 @@ class CommitInfoManager { * @param displayCommit Commit to display the information of. * @return ftxui::Element */ - static ftxui::Element renderInfoView(const cpplibostree::Commit& displayCommit); + [[nodiscard]] static ftxui::Element renderInfoView(const cpplibostree::Commit& displayCommit); }; class BranchBoxManager { public: - ftxui::Component branchBoxes = ftxui::Container::Vertical({}); - - public: - BranchBoxManager(cpplibostree::OSTreeRepo& repo, + BranchBoxManager(OSTreeTUI& ostreetui, + cpplibostree::OSTreeRepo& repo, std::unordered_map& visibleBranches); /** @@ -64,5 +60,8 @@ class BranchBoxManager { * * @return ftxui::Element */ - ftxui::Element branchBoxRender(); + [[nodiscard]] ftxui::Element branchBoxRender(); + + public: + ftxui::Component branchBoxes = ftxui::Container::Vertical({}); }; diff --git a/src/main.cpp b/src/main.cpp index 98d6f66..98f5efd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -58,5 +58,5 @@ int main(int argc, const char** argv) { // OSTree TUI OSTreeTUI ostreetui(repo, startupBranches); - return ostreetui.run(); + return ostreetui.Run(); } diff --git a/src/util/cpplibostree.cpp b/src/util/cpplibostree.cpp index 24bcbcb..9673cdd 100644 --- a/src/util/cpplibostree.cpp +++ b/src/util/cpplibostree.cpp @@ -8,7 +8,7 @@ #include #include // C -#include +#include #include #include #include @@ -314,7 +314,7 @@ std::string OSTreeRepo::getBranchesAsString() { } /// TODO This implementation should not rely on the ostree CLI -> change to libostree usage. -bool OSTreeRepo::promoteCommit(const std::string& hash, +bool OSTreeRepo::PromoteCommit(const std::string& hash, const std::string& newRef, const std::vector addMetadataStrings, const std::string& newSubject, diff --git a/src/util/cpplibostree.hpp b/src/util/cpplibostree.hpp index 1210ea4..d10b869 100644 --- a/src/util/cpplibostree.hpp +++ b/src/util/cpplibostree.hpp @@ -148,7 +148,7 @@ class OSTreeRepo { * @return true on success * @return false on failed promotion */ - bool promoteCommit(const std::string& hash, + bool PromoteCommit(const std::string& hash, const std::string& newRef, const std::vector addMetadataStrings, const std::string& newSubject = "",