Skip to content

Commit

Permalink
Add git tui log
Browse files Browse the repository at this point in the history
This provides an UI to `git log`.
  • Loading branch information
ArthurSonzogni committed May 24, 2021
1 parent c99f713 commit fb5e86c
Show file tree
Hide file tree
Showing 11 changed files with 1,536 additions and 298 deletions.
6 changes: 3 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ if(NOT ftxui_POPULATED)
FetchContent_Populate(ftxui)
add_subdirectory(${ftxui_SOURCE_DIR} ${ftxui_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()

# ------------------------------------------------------------------------------

set(git_tag 0)
Expand All @@ -40,11 +39,12 @@ project(git-tui
add_executable(git-tui
src/diff.cpp
src/diff.hpp
src/log.cpp
src/log.hpp
src/main.cpp
src/scroller.cpp
src/scroller.hpp
src/exec.cpp
src/exec.hpp
src/process.hpp
)

target_include_directories(git-tui
Expand Down
268 changes: 263 additions & 5 deletions src/diff.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
#include "diff.hpp"
#include "diff.hpp" // for File, Line, Hunk, Parse, Line::Add, Line::Delete, Line::Keep

#include <assert.h> // for assert
#include <algorithm> // for max
#include <stdlib.h> // for EXIT_SUCCESS
#include <ftxui/screen/string.hpp> // for to_wstring
#include <iostream> // for stringstream, basic_ios, basic_istream
#include <memory> // for allocator_traits<>::value_type
#include <iostream> // for operator<<, stringstream, endl, basic_ios, basic_istream, basic_ostream, cout, ostream
#include <memory> // for allocator_traits<>::value_type, shared_ptr, __shared_ptr_access
#include <regex> // for regex_match, match_results, match_results<>::_Base_type, sub_match, regex, smatch
#include <string> // for wstring, allocator, operator+, basic_string, char_traits, string, stoi, getline, to_string
#include <utility> // for move
#include <vector> // for vector

#include "ftxui/component/captured_mouse.hpp" // for ftxui
#include "ftxui/component/component.hpp" // for Renderer, Button, Horizontal, CatchEvent, Checkbox, Menu, Vertical
#include "ftxui/component/component_base.hpp" // for ComponentBase
#include "ftxui/component/event.hpp" // for Event
#include "ftxui/component/screen_interactive.hpp" // for ScreenInteractive
#include "ftxui/dom/elements.hpp" // for text, operator|, vbox, separator, Element, Elements, filler, bgcolor, size, window, xflex, color, hbox, dim, EQUAL, WIDTH, xflex_grow, xflex_shrink, yflex
#include "ftxui/screen/color.hpp" // for Color, Color::Black, Color::White
#include "process.hpp"
#include "scroller.hpp" // for Scroller

using namespace ftxui;

namespace diff {

std::vector<File> Parse(std::string input) {
std::stringstream ss(input);
Expand Down Expand Up @@ -39,7 +56,7 @@ std::vector<File> Parse(std::string input) {

if (start_with("+++") && parse_header) {
assert(files.size() != 0);
files.back().right_file = getw().substr(3);
files.back().right_file = getw().substr(6);
continue;
}

Expand Down Expand Up @@ -81,6 +98,247 @@ std::vector<File> Parse(std::string input) {
return files;
}

Element RenderSplit(const Hunk& hunk) {
Elements left_line_numbers;
Elements left_lines;
Elements right_line_numbers;
Elements right_lines;
int left_line_number = hunk.left_start;
int right_line_number = hunk.right_start;
auto stabilize = [&] {
while (left_lines.size() < right_lines.size()) {
left_lines.push_back(text(L""));
left_line_numbers.push_back(text(L"~") | dim);
}
while (left_lines.size() > right_lines.size()) {
right_lines.push_back(text(L""));
right_line_numbers.push_back(text(L"~") | dim);
}
};
for (const Line& line : hunk.lines) {
switch (line.type) {
case Line::Keep:
stabilize();
left_line_numbers.push_back(text(to_wstring(left_line_number++)));
right_line_numbers.push_back(text(to_wstring(right_line_number++)));
left_lines.push_back(text(line.content));
right_lines.push_back(text(line.content));
break;

case Line::Delete:
left_line_numbers.push_back(text(to_wstring(left_line_number++)));
left_lines.push_back(text(line.content) |
color(Color::RGB(255, 200, 200)) |
bgcolor(Color::RGB(128, 0, 0)));
break;
case Line::Add:
right_line_numbers.push_back(text(to_wstring(right_line_number++)));
right_lines.push_back(text(line.content) |
color(Color::RGB(200, 255, 200)) |
bgcolor(Color::RGB(0, 128, 0)));
break;
}
}
stabilize();
return hbox({
vbox(std::move(left_line_numbers)),
separator(),
vbox(std::move(left_lines)) | size(WIDTH, EQUAL, 800) | xflex,
separator(),
vbox(std::move(right_line_numbers)),
separator(),
vbox(std::move(right_lines)) | size(WIDTH, EQUAL, 800) | xflex,
}) |
xflex;
}

Element RenderJoin(const Hunk& hunk) {
Elements left_line_numbers;
Elements right_line_numbers;
Elements lines;
int left_line_number = hunk.left_start;
int right_line_number = hunk.right_start;
for (const Line& line : hunk.lines) {
switch (line.type) {
case Line::Keep:
left_line_numbers.push_back(text(to_wstring(left_line_number++)));
right_line_numbers.push_back(text(to_wstring(right_line_number++)));
lines.push_back(text(line.content));
break;

case Line::Delete:
left_line_numbers.push_back(text(to_wstring(left_line_number++)));
right_line_numbers.push_back(text(L"~") | dim);
lines.push_back(text(line.content) | color(Color::RGB(255, 200, 200)) |
bgcolor(Color::RGB(128, 0, 0)));
break;
case Line::Add:
left_line_numbers.push_back(text(L"~") | dim);
right_line_numbers.push_back(text(to_wstring(right_line_number++)));
lines.push_back(text(line.content) | color(Color::RGB(200, 255, 200)) |
bgcolor(Color::RGB(0, 128, 0)));
break;
}
}
return hbox({
vbox(std::move(left_line_numbers)),
separator(),
vbox(std::move(right_line_numbers)),
separator(),
vbox(std::move(lines)) | size(WIDTH, EQUAL, 800) | xflex,
}) |
xflex;
}

Element Render(const File& file, bool split) {
Elements hunks;
bool is_first = true;
for (const Hunk& hunk : file.hunks) {
if (!is_first)
hunks.push_back(separator());
is_first = false;
if (split)
hunks.push_back(RenderSplit(hunk));
else
hunks.push_back(RenderJoin(hunk));
}
return vbox(std::move(hunks));
}

int main(int argc, const char** argv) {
using namespace ftxui;

std::string args;
for (int i = 0; i < argc; ++i) {
args += argv[i];
args += " ";
}

int hunk_size = 3;

std::vector<File> files;
std::vector<std::wstring> file_menu_entries;

auto refresh_data = [&] {
files.clear();
file_menu_entries.clear();

procxx::process git("git");
git.add_argument("diff");
git.add_argument("-U" + std::to_string(hunk_size));
for(int i = 0; i<argc; ++i)
git.add_argument(argv[i]);
git.exec();

std::string diff(std::istreambuf_iterator<char>(git.output()),
std::istreambuf_iterator<char>());
files = Parse(diff);
for (const auto& file : files)
file_menu_entries.push_back(file.right_file);
};
refresh_data();

if (files.size() == 0) {
std::cout << "No difference..." << std::endl;
return EXIT_SUCCESS;
}

auto increase_hunk = [&] {
hunk_size++;
refresh_data();
};
auto decrease_hunk = [&] {
if (hunk_size != 0)
hunk_size--;
refresh_data();
};
auto button_increase_hunk = Button("[+1]", increase_hunk, false);
auto button_decrease_hunk = Button("[-1]", decrease_hunk, false);

// File menu.
int file_menu_selected = 0;
auto file_menu = Menu(&file_menu_entries, &file_menu_selected);

bool split = true;
auto split_checkbox = Checkbox("[S]plit", &split);

auto scroller = Scroller(
Renderer([&] { return Render(files[file_menu_selected], split); }));

auto container = Container::Horizontal({
file_menu,
scroller,
});

container = Renderer(container, [&] {
const File& file = files[file_menu_selected];
return hbox({
window(text(L" Files "), file_menu->Render()) | xflex_grow,
vbox({
window(
text(L" Difference "),
vbox({text(file.left_file + L" -> " + file.right_file),
separator(), scroller->Render()})),
filler(),
}) | xflex_shrink,
}) |
bgcolor(Color::RGB(30, 30, 30)) | yflex;
});

auto options = Container::Horizontal({
split_checkbox,
button_decrease_hunk,
button_increase_hunk,
});

auto option_renderer = Renderer(options, [&] {
return hbox({
text(L"[git tui diff]"),
filler(),
split_checkbox->Render(),
text(L" Context:"),
button_decrease_hunk->Render(),
text(to_wstring(hunk_size)),
button_increase_hunk->Render(),
filler(),
}) |
bgcolor(Color::White) | color(Color::Black);
});

container = Container::Vertical({
option_renderer,
container,
});

container = CatchEvent(container, [&](Event event) {
if (event == Event::Character('s')) {
split = !split;
return true;
}

if (event == Event::Character('-')) {
decrease_hunk();
return true;
}

if (event == Event::Character('+')) {
increase_hunk();
return true;
}

return false;
});

file_menu->TakeFocus();

auto screen = ScreenInteractive::Fullscreen();
screen.Loop(container);

return EXIT_SUCCESS;
}

} // namespace diff

// Copyright 2021 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
10 changes: 10 additions & 0 deletions src/diff.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

#include <string>
#include <vector>
#include <ftxui/dom/elements.hpp>

namespace diff {
using namespace ftxui;

struct Line {
enum Type {
Expand All @@ -27,6 +31,12 @@ struct File {
};

std::vector<File> Parse(std::string input);
Element RenderSplit(const Hunk& hunk);
Element RenderJoin(const Hunk& hunk);
Element Render(const File& file, bool split);
int main(int argc, const char** argv);

} // namespace diff

#endif /* end of include guard: GIT_DIFF_TUI_DIFF_HPP */

Expand Down
2 changes: 1 addition & 1 deletion src/environment.h.in
Original file line number Diff line number Diff line change
@@ -1 +1 @@
const char* project_version = "@PROJECT_VERSION@";
static const char project_version[] = "@PROJECT_VERSION@";
23 changes: 0 additions & 23 deletions src/exec.cpp

This file was deleted.

12 changes: 0 additions & 12 deletions src/exec.hpp

This file was deleted.

Loading

0 comments on commit fb5e86c

Please sign in to comment.