From 2bc5d65251d98815878787ad17d7c2d57b4b5cf5 Mon Sep 17 00:00:00 2001 From: Ole Christian Eidheim Date: Wed, 22 Jun 2016 14:01:10 +0200 Subject: [PATCH] Git integration through libgit2 (#244) * Git integration, fixes #63 * Fixed a crash when deleting directories, added libgit2 to MSYS2 CI, adjusted colors slightly * Git integration now supports debian stable * Fixed compilation error on MSYS2 * Added git_test * git_test fix * Git integration: now updates correct paths on source save. Also added slight delay to source diff git monitor change signal * git_test fixed * Now monitors .git directory instead. The .git/index file does not always update on for instance: git commit -m ... * Directories cleanup * Fixed git status update on rename refactoring, and some additional cleanup * Added menu items: Go to Next Diff, and Show Diff * Fixed Go to Next Diff and Show Diff keybindings * Minor fixes to git integration * Added: implement method * Minor fixes to Implement Method * Minor fixes to source_diff * source_diff: optimisations added, as well as some minor improvements * Fixed a crash when trying to show diff in a buffer not related to a diff repository * Git integration: MSYS2 support * source_diff: source should now refresh correctly when .git directory has changed * directories.cc: stop updating parent path colors when path including .git directory/file is found * Spellcheck underline no longer shows for for instance '\n' * Made directory view's git status update async * Use boost::filesystem::path in git.* * Optimisation: now stores a cache of git status, which can be slow, that is used when possible * Source view will now grab focus when a selection dialog is shown * Source menu should now be correctly updated * Implement Method: improved * git.cc: minor fix --- CMakeLists.txt | 1 + README.md | 2 + ci/update_ci.sh | 2 +- docs/install.md | 16 +- src/CMakeLists.txt | 9 +- src/config.cc | 7 + src/config.h | 1 + src/directories.cc | 288 ++++++++++++++++++++--------- src/directories.h | 28 ++- src/files.h | 27 ++- src/git.cc | 284 ++++++++++++++++++++++++++++ src/git.h | 102 ++++++++++ src/menu.cc | 20 ++ src/notebook.cc | 4 +- src/selectiondialog.cc | 1 + src/source.cc | 5 +- src/source.h | 7 +- src/source_clang.cc | 67 +++++++ src/source_diff.cc | 369 +++++++++++++++++++++++++++++++++++++ src/source_diff.h | 71 +++++++ src/source_spellcheck.cc | 24 +-- src/source_spellcheck.h | 2 +- src/window.cc | 59 ++++++ src/window.h | 1 - tests/CMakeLists.txt | 13 +- tests/git_test.cc | 41 +++++ tests/stubs/directories.cc | 11 ++ 27 files changed, 1333 insertions(+), 129 deletions(-) create mode 100644 src/git.cc create mode 100644 src/git.h create mode 100644 src/source_diff.cc create mode 100644 src/source_diff.h create mode 100644 tests/git_test.cc create mode 100644 tests/stubs/directories.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index eecdb95..de149ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ find_package(ASPELL REQUIRED) include(FindPkgConfig) pkg_check_modules(GTKMM gtkmm-3.0 REQUIRED) pkg_check_modules(GTKSVMM gtksourceviewmm-3.0 REQUIRED) +pkg_check_modules(LIBGIT2 libgit2 REQUIRED) add_subdirectory("src") diff --git a/README.md b/README.md index 20ee72a..e704961 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ towards libclang with speed and ease of use in mind. * C++ Fix-its * Debug integration, both local and remote, through lldb * Automated CMake processing, including support for external libraries +* Git support through libgit2 * Fast C++ autocompletion * Keyword and buffer autocompletion for other file types * Tooltips showing type information and doxygen documentation (C++) @@ -48,6 +49,7 @@ See [enhancements](https://github.com/cppit/jucipp/labels/enhancement) for plann * aspell * libclang * lldb +* libgit2 * [libclangmm](http://github.com/cppit/libclangmm/) (downloaded directly with git --recursive, no need to install) * [tiny-process-library](http://github.com/eidheim/tiny-process-library/) (downloaded directly with git --recursive, no need to install) diff --git a/ci/update_ci.sh b/ci/update_ci.sh index 795ac87..93ebb0e 100755 --- a/ci/update_ci.sh +++ b/ci/update_ci.sh @@ -32,7 +32,7 @@ function windows () { if [ "$PLATFORM" == "x86" ]; then arch=i686 fi - sh -c "pacman -S --noconfirm git mingw-w64-${arch}-cmake make mingw-w64-${arch}-toolchain mingw-w64-${arch}-clang mingw-w64-${arch}-gtkmm3 mingw-w64-${arch}-gtksourceviewmm3 mingw-w64-${arch}-boost mingw-w64-${arch}-aspell mingw-w64-${arch}-aspell-en" + sh -c "pacman -S --noconfirm git mingw-w64-${arch}-cmake make mingw-w64-${arch}-toolchain mingw-w64-${arch}-clang mingw-w64-${arch}-gtkmm3 mingw-w64-${arch}-gtksourceviewmm3 mingw-w64-${arch}-boost mingw-w64-${arch}-aspell mingw-w64-${arch}-aspell-en mingw-w64-${arch}-libgit2" } if [ "$TRAVIS_OS_NAME" == "" ]; then diff --git a/docs/install.md b/docs/install.md index 9719b28..5aa5d1b 100644 --- a/docs/install.md +++ b/docs/install.md @@ -16,7 +16,7 @@ ## Debian testing/Linux Mint/Ubuntu Install dependencies: ```sh -sudo apt-get install git cmake make g++ libclang-3.6-dev liblldb-3.6-dev clang-format-3.6 pkg-config libboost-filesystem-dev libboost-regex-dev libgtksourceviewmm-3.0-dev aspell-en libaspell-dev +sudo apt-get install git cmake make g++ libclang-3.6-dev liblldb-3.6-dev clang-format-3.6 pkg-config libboost-filesystem-dev libboost-regex-dev libgtksourceviewmm-3.0-dev aspell-en libaspell-dev libgit2-dev ``` Get juCi++ source, compile and install: @@ -32,7 +32,7 @@ sudo make install ## Debian stable/Linux Mint Debian Edition/Raspbian Install dependencies: ```sh -sudo apt-get install git cmake make g++ libclang-3.5-dev liblldb-3.5-dev clang-format-3.5 pkg-config libboost-filesystem-dev libboost-regex-dev libgtksourceviewmm-3.0-dev aspell-en libaspell-dev +sudo apt-get install git cmake make g++ libclang-3.5-dev liblldb-3.5-dev clang-format-3.5 pkg-config libboost-filesystem-dev libboost-regex-dev libgtksourceviewmm-3.0-dev aspell-en libaspell-dev libgit2-dev ``` Get juCi++ source, compile and install: @@ -58,7 +58,7 @@ Alternatively, follow the instructions below. Install dependencies: ```sh -sudo pacman -S git cmake pkg-config make clang lldb gtksourceviewmm boost aspell aspell-en +sudo pacman -S git cmake pkg-config make clang lldb gtksourceviewmm boost aspell aspell-en libgit2 ``` Get juCi++ source, compile and install: @@ -74,7 +74,7 @@ sudo make install ## Fedora 23 Install dependencies: ```sh -sudo dnf install git cmake make gcc-c++ clang-devel clang lldb-devel boost-devel gtksourceviewmm3-devel gtkmm30-devel aspell-devel aspell-en +sudo dnf install git cmake make gcc-c++ clang-devel clang lldb-devel boost-devel gtksourceviewmm3-devel gtkmm30-devel aspell-devel aspell-en libgit2-devel ``` Get juCi++ source, compile and install: @@ -96,12 +96,12 @@ Install dependencies: 32-bit: ```sh -sudo urpmi git cmake make gcc-c++ clang libclang-devel libboost-devel libgtkmm3.0-devel libgtksourceviewmm3.0-devel libaspell-devel aspell-en +sudo urpmi git cmake make gcc-c++ clang libclang-devel libboost-devel libgtkmm3.0-devel libgtksourceviewmm3.0-devel libaspell-devel aspell-en libgit2-devel ``` 64-bit: ```sh -sudo urpmi git cmake make gcc-c++ clang lib64clang-devel lib64boost-devel lib64gtkmm3.0-devel lib64gtksourceviewmm3.0-devel lib64aspell-devel aspell-en +sudo urpmi git cmake make gcc-c++ clang lib64clang-devel lib64boost-devel lib64gtkmm3.0-devel lib64gtksourceviewmm3.0-devel lib64aspell-devel aspell-en libgit2-devel ``` Get juCi++ source, compile and install: @@ -120,7 +120,7 @@ sudo make install Install dependencies: ```sh brew install --with-clang --with-lldb llvm -brew install cmake pkg-config boost homebrew/x11/gtksourceviewmm3 aspell clang-format +brew install cmake pkg-config boost homebrew/x11/gtksourceviewmm3 aspell clang-format libgit2 ``` Get juCi++ source, compile and install: @@ -138,7 +138,7 @@ make install Install dependencies (replace `x86_64` with `i686` for 32-bit MSYS2 installs): ```sh -pacman -S git mingw-w64-x86_64-cmake make mingw-w64-x86_64-toolchain mingw-w64-x86_64-clang mingw-w64-x86_64-gtkmm3 mingw-w64-x86_64-gtksourceviewmm3 mingw-w64-x86_64-boost mingw-w64-x86_64-aspell mingw-w64-x86_64-aspell-en +pacman -S git mingw-w64-x86_64-cmake make mingw-w64-x86_64-toolchain mingw-w64-x86_64-clang mingw-w64-x86_64-gtkmm3 mingw-w64-x86_64-gtksourceviewmm3 mingw-w64-x86_64-boost mingw-w64-x86_64-aspell mingw-w64-x86_64-aspell-en mingw-w64-x86_64-libgit2 ``` Note that juCi++ must be built and run in a MinGW Shell (for instance MinGW-w64 Win64 Shell). diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1252cb6..fb04df6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,18 +19,23 @@ set(global_includes ${GTKSVMM_INCLUDE_DIRS} ${LIBCLANG_INCLUDE_DIRS} ${ASPELL_INCLUDE_DIR} + ${LIBGIT2_INCLUDE_DIRS} ../libclangmm/src ../tiny-process-library . ) -set(global_libraries +if(MSYS) + set(global_libraries winpthread) +endif() +set(global_libraries ${global_libraries} ${GTKMM_LIBRARIES} ${GTKSVMM_LIBRARIES} ${Boost_LIBRARIES} ${LIBCLANG_LIBRARIES} ${LIBLLDB_LIBRARIES} ${ASPELL_LIBRARIES} + ${LIBGIT2_LIBRARIES} ) set(project_files @@ -55,9 +60,11 @@ set(project_shared_files cmake.cc dispatcher.cc filesystem.cc + git.cc project_build.cc source.cc source_clang.cc + source_diff.cc source_spellcheck.cc ../libclangmm/src/CodeCompleteResults.cc diff --git a/src/config.cc b/src/config.cc index 750f39c..2d7157b 100644 --- a/src/config.cc +++ b/src/config.cc @@ -163,6 +163,11 @@ void Config::update_config_file() { cfg_ok=false; if(cfg.count("version")>0) cfg.find("version")->second.data()=default_cfg.get("version"); + + auto style_path=home/"styles"; + filesystem::write(style_path/"juci-light.xml", juci_light_style); + filesystem::write(style_path/"juci-dark.xml", juci_dark_style); + filesystem::write(style_path/"juci-dark-blue.xml", juci_dark_blue_style); } else return; @@ -188,6 +193,8 @@ void Config::get_source() { source.show_map = source_json.get("show_map"); source.map_font_size = source_json.get("map_font_size"); + + source.show_git_diff = source_json.get("show_git_diff"); source.spellcheck_language = source_json.get("spellcheck_language"); diff --git a/src/config.h b/src/config.h index 60d318e..d537c6a 100644 --- a/src/config.h +++ b/src/config.h @@ -60,6 +60,7 @@ public: bool show_map; std::string map_font_size; + bool show_git_diff; bool auto_tab_char_and_size; char default_tab_char; diff --git a/src/directories.cc b/src/directories.cc index f3e08fa..8318cab 100644 --- a/src/directories.cc +++ b/src/directories.cc @@ -1,6 +1,5 @@ #include "directories.h" #include -#include #include "source.h" #include "terminal.h" #include "notebook.h" @@ -95,12 +94,18 @@ bool Directories::TreeStore::drag_data_received_vfunc(const TreeModel::Path &pat auto new_file_path=target_path; for(;file_it!=view->file_path.end();file_it++) new_file_path/=*file_it; - view->file_path=new_file_path; + { + std::unique_lock lock(view->file_path_mutex); + view->file_path=new_file_path; + } g_signal_emit_by_name(view->get_buffer()->gobj(), "modified_changed"); } } else if(view->file_path==source_path) { - view->file_path=target_path; + { + std::unique_lock lock(view->file_path_mutex); + view->file_path=target_path; + } g_signal_emit_by_name(view->get_buffer()->gobj(), "modified_changed"); break; } @@ -118,7 +123,7 @@ bool Directories::TreeStore::drag_data_delete_vfunc (const Gtk::TreeModel::Path return false; } -Directories::Directories() : Gtk::TreeView(), stop_update_thread(false) { +Directories::Directories() : Gtk::TreeView() { this->set_enable_tree_lines(true); tree_store = TreeStore::create(); @@ -148,22 +153,17 @@ Directories::Directories() : Gtk::TreeView(), stop_update_thread(false) { }); signal_test_expand_row().connect([this](const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path){ - if(iter->children().begin()->get_value(column_record.path)=="") { - std::unique_lock lock(update_mutex); - add_path(iter->get_value(column_record.path), *iter); - } + if(iter->children().begin()->get_value(column_record.path)=="") + add_or_update_path(iter->get_value(column_record.path), *iter, true); return false; }); signal_row_collapsed().connect([this](const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path){ - { - std::unique_lock lock(update_mutex); - auto directory_str=iter->get_value(column_record.path).string(); - for(auto it=last_write_times.begin();it!=last_write_times.end();) { - if(directory_str==it->first.substr(0, directory_str.size())) - it=last_write_times.erase(it); - else - it++; - } + auto directory_str=iter->get_value(column_record.path).string(); + for(auto it=directories.begin();it!=directories.end();) { + if(directory_str==it->first.substr(0, directory_str.size())) + it=directories.erase(it); + else + it++; } auto children=iter->children(); if(children) { @@ -172,35 +172,7 @@ Directories::Directories() : Gtk::TreeView(), stop_update_thread(false) { } auto child=tree_store->append(iter->children()); child->set_value(column_record.name, std::string("(empty)")); - Gdk::RGBA rgba; - rgba.set_rgba(0.5, 0.5, 0.5); - child->set_value(column_record.color, rgba); - } - }); - - update_thread=std::thread([this](){ - while(!stop_update_thread) { - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); - std::unique_lock lock(update_mutex); - for(auto it=last_write_times.begin();it!=last_write_times.end();) { - boost::system::error_code ec; - auto last_write_time=boost::filesystem::last_write_time(it->first, ec); - auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - if(!ec) { - if(last_write_time!=now && it->second.second(it->first); - dispatcher.post([this, path, last_write_time] { - std::unique_lock lock(update_mutex); - auto it=last_write_times.find(*path); - if(it!=last_write_times.end()) - add_path(*path, it->second.first, last_write_time); - }); - } - it++; - } - else - it=last_write_times.erase(it); - } + child->set_value(column_record.type, PathType::UNKNOWN); } }); @@ -330,12 +302,18 @@ Directories::Directories() : Gtk::TreeView(), stop_update_thread(false) { auto new_file_path=target_path; for(;file_it!=view->file_path.end();file_it++) new_file_path/=*file_it; - view->file_path=new_file_path; + { + std::unique_lock lock(view->file_path_mutex); + view->file_path=new_file_path; + } g_signal_emit_by_name(view->get_buffer()->gobj(), "modified_changed"); } } else if(view->file_path==*source_path) { - view->file_path=target_path; + { + std::unique_lock lock(view->file_path_mutex); + view->file_path=target_path; + } g_signal_emit_by_name(view->get_buffer()->gobj(), "modified_changed"); std::string old_language_id; @@ -415,8 +393,6 @@ Directories::Directories() : Gtk::TreeView(), stop_update_thread(false) { } Directories::~Directories() { - stop_update_thread=true; - update_thread.join(); dispatcher.disconnect(); } @@ -425,11 +401,6 @@ void Directories::open(const boost::filesystem::path &dir_path) { return; tree_store->clear(); - { - std::unique_lock lock(update_mutex); - last_write_times.clear(); - } - //TODO: report that set_title does not handle '_' correctly? auto title=dir_path.filename().string(); @@ -440,21 +411,31 @@ void Directories::open(const boost::filesystem::path &dir_path) { } get_column(0)->set_title(title); - { - std::unique_lock lock(update_mutex); - add_path(dir_path, Gtk::TreeModel::Row()); + for(auto &directory: directories) { + if(directory.second.repository) + directory.second.repository->clear_saved_status(); } - + directories.clear(); + add_or_update_path(dir_path, Gtk::TreeModel::Row(), true); + path=dir_path; } void Directories::update() { - { - std::unique_lock lock(update_mutex); - for(auto &last_write_time: last_write_times) { - add_path(last_write_time.first, last_write_time.second.first); - } - } + std::vector > saved_directories; + for(auto &directory: directories) + saved_directories.emplace_back(directory.first, directory.second.row); + for(auto &directory: saved_directories) + add_or_update_path(directory.first, directory.second, false); +} + +void Directories::on_save_file(boost::filesystem::path file_path) { + auto it=directories.find(file_path.parent_path().string()); + if(it!=directories.end()) { + if(it->second.repository) + it->second.repository->clear_saved_status(); + colorize_path(it->first, true); + } } void Directories::select(const boost::filesystem::path &select_path) { @@ -479,12 +460,7 @@ void Directories::select(const boost::filesystem::path &select_path) { parent_path=select_path.parent_path(); //check if select_path is already expanded - size_t expanded; - { - std::unique_lock lock(update_mutex); - expanded=last_write_times.find(parent_path.string())!=last_write_times.end(); - } - if(expanded) { + if(directories.find(parent_path.string())!=directories.end()) { //set cursor at select_path and return tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter){ if(iter->get_value(column_record.path)==select_path) { @@ -508,8 +484,7 @@ void Directories::select(const boost::filesystem::path &select_path) { for(auto &a_path: paths) { tree_store->foreach_iter([this, &a_path](const Gtk::TreeModel::iterator &iter){ if(iter->get_value(column_record.path)==a_path) { - std::unique_lock lock(update_mutex); - add_path(a_path, *iter); + add_or_update_path(a_path, *iter, true); return true; } return false; @@ -557,16 +532,71 @@ bool Directories::on_button_press_event(GdkEventButton* event) { return Gtk::TreeView::on_button_press_event(event); } -void Directories::add_path(const boost::filesystem::path &dir_path, const Gtk::TreeModel::Row &parent, time_t last_write_time) { - boost::system::error_code ec; - if(last_write_time==0) - last_write_time=boost::filesystem::last_write_time(dir_path, ec); - if(ec) +void Directories::add_or_update_path(const boost::filesystem::path &dir_path, const Gtk::TreeModel::Row &row, bool include_parent_paths) { + auto path_it=directories.find(dir_path.string()); + if(!boost::filesystem::exists(dir_path)) { + if(path_it!=directories.end()) + directories.erase(path_it); return; - last_write_times[dir_path.string()]={parent, last_write_time}; + } + + if(path_it==directories.end()) { + auto g_file=Glib::wrap(g_file_new_for_path(dir_path.string().c_str())); //TODO: report missing constructor in giomm + +#if GLIB_CHECK_VERSION(2, 44, 0) + auto monitor=g_file->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_WATCH_MOVES); +#else + auto monitor=g_file->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_SEND_MOVED); +#endif + auto path_and_row=std::make_shared >(dir_path, row); + auto connection=std::make_shared(); + + std::shared_ptr repository; + try { + repository=Git::get().get_repository(dir_path); + } + catch(const std::exception &) {} + + monitor->signal_changed().connect([this, connection, path_and_row, repository] (const Glib::RefPtr &file, + const Glib::RefPtr&, + Gio::FileMonitorEvent monitor_event) { + if(monitor_event!=Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + if(repository) + repository->clear_saved_status(); + connection->disconnect(); + *connection=Glib::signal_timeout().connect([path_and_row, this]() { + add_or_update_path(path_and_row->first, path_and_row->second, true); + return false; + }, 500); + } + }); + + std::shared_ptr repository_connection(new sigc::connection(), [](sigc::connection *connection) { + connection->disconnect(); + delete connection; + }); + + if(repository) { + auto connection=std::make_shared(); + *repository_connection=repository->monitor->signal_changed().connect([this, connection, path_and_row](const Glib::RefPtr &file, + const Glib::RefPtr&, + Gio::FileMonitorEvent monitor_event) { + if(monitor_event!=Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + connection->disconnect(); + *connection=Glib::signal_timeout().connect([this, path_and_row] { + if(directories.find(path_and_row->first.string())!=directories.end()) + colorize_path(path_and_row->first, false); + return false; + }, 500); + } + }); + } + directories[dir_path.string()]={row, monitor, repository, repository_connection}; + } + std::unique_ptr children; //Gtk::TreeNodeChildren is missing default constructor... - if(parent) - children=std::unique_ptr(new Gtk::TreeNodeChildren(parent.children())); + if(row) + children=std::unique_ptr(new Gtk::TreeNodeChildren(row.children())); else children=std::unique_ptr(new Gtk::TreeNodeChildren(tree_store->children())); if(*children) { @@ -596,19 +626,14 @@ void Directories::add_path(const boost::filesystem::path &dir_path, const Gtk::T child->set_value(column_record.id, "a"+filename); auto grandchild=tree_store->append(child->children()); grandchild->set_value(column_record.name, std::string("(empty)")); - Gdk::RGBA rgba; - rgba.set_rgba(0.5, 0.5, 0.5); - grandchild->set_value(column_record.color, rgba); + grandchild->set_value(column_record.type, PathType::UNKNOWN); } else { child->set_value(column_record.id, "b"+filename); auto language=Source::guess_language(it->path().filename()); - if(!language) { - Gdk::RGBA rgba; - rgba.set_rgba(0.5, 0.5, 0.5); - child->set_value(column_record.color, rgba); - } + if(!language) + child->set_value(column_record.type, PathType::UNKNOWN); } } } @@ -624,8 +649,87 @@ void Directories::add_path(const boost::filesystem::path &dir_path, const Gtk::T if(!*children) { auto child=tree_store->append(*children); child->set_value(column_record.name, std::string("(empty)")); - Gdk::RGBA rgba; - rgba.set_rgba(0.5, 0.5, 0.5); - child->set_value(column_record.color, rgba); + child->set_value(column_record.type, PathType::UNKNOWN); + } + + colorize_path(dir_path, include_parent_paths); +} + +void Directories::colorize_path(const boost::filesystem::path &dir_path_, bool include_parent_paths) { + auto it=directories.find(dir_path_.string()); + if(it==directories.end()) + return; + + if(it!=directories.end() && it->second.repository) { + auto dir_path=std::make_shared(dir_path_); + auto repository=it->second.repository; + std::thread git_status_thread([this, dir_path, repository, include_parent_paths] { + auto status=std::make_shared(); + try { + *status=repository->get_status(); + } + catch(const std::exception &e) { + Terminal::get().async_print(std::string("Error (git): ")+e.what()+'\n', true); + } + + dispatcher.post([this, dir_path, include_parent_paths, status] { + auto it=directories.find(dir_path->string()); + if(it==directories.end()) + return; + + auto normal_color=get_style_context()->get_color(Gtk::StateFlags::STATE_FLAG_NORMAL); + Gdk::RGBA gray; + gray.set_rgba(0.5, 0.5, 0.5); + Gdk::RGBA yellow; + yellow.set_rgba(1.0, 1.0, 0.2); + double factor=0.5; + yellow.set_red(normal_color.get_red()+factor*(yellow.get_red()-normal_color.get_red())); + yellow.set_green(normal_color.get_green()+factor*(yellow.get_green()-normal_color.get_green())); + yellow.set_blue(normal_color.get_blue()+factor*(yellow.get_blue()-normal_color.get_blue())); + Gdk::RGBA green; + green.set_rgba(0.0, 1.0, 0.0); + factor=0.4; + green.set_red(normal_color.get_red()+factor*(green.get_red()-normal_color.get_red())); + green.set_green(normal_color.get_green()+factor*(green.get_green()-normal_color.get_green())); + green.set_blue(normal_color.get_blue()+factor*(green.get_blue()-normal_color.get_blue())); + + do { + std::unique_ptr children; //Gtk::TreeNodeChildren is missing default constructor... + if(it->second.row) + children=std::unique_ptr(new Gtk::TreeNodeChildren(it->second.row.children())); + else + children=std::unique_ptr(new Gtk::TreeNodeChildren(tree_store->children())); + if(!*children) + return; + + for(auto &child: *children) { + auto path=child.get_value(column_record.path); + if(status->modified.find(path.generic_string())!=status->modified.end()) + child.set_value(column_record.color, yellow); + else if(status->added.find(path.generic_string())!=status->added.end()) + child.set_value(column_record.color, green); + else { + auto type=child.get_value(column_record.type); + if(type==PathType::UNKNOWN) + child.set_value(column_record.color, gray); + else + child.set_value(column_record.color, normal_color); + } + } + + if(!include_parent_paths) + break; + + auto path=boost::filesystem::path(it->first); + if(boost::filesystem::exists(path/".git")) + break; + if(path==path.root_directory()) + break; + auto parent_path=boost::filesystem::path(it->first).parent_path(); + it=directories.find(parent_path.string()); + } while(it!=directories.end()); + }); + }); + git_status_thread.detach(); } } diff --git a/src/directories.h b/src/directories.h index 2d0fbf8..232eb5e 100644 --- a/src/directories.h +++ b/src/directories.h @@ -9,10 +9,21 @@ #include #include #include +#include +#include "git.h" #include "dispatcher.h" class Directories : public Gtk::TreeView { -public: + class DirectoryData { + public: + Gtk::TreeModel::Row row; + Glib::RefPtr monitor; + std::shared_ptr repository; + std::shared_ptr connection; + }; + + enum class PathType {KNOWN, UNKNOWN}; + class TreeStore : public Gtk::TreeStore { protected: TreeStore() {} @@ -28,18 +39,19 @@ public: add(id); add(name); add(path); + add(type); add(color); } Gtk::TreeModelColumn id; Gtk::TreeModelColumn name; Gtk::TreeModelColumn path; + Gtk::TreeModelColumn type; Gtk::TreeModelColumn color; }; static Glib::RefPtr create() {return Glib::RefPtr(new TreeStore());} }; -private: Directories(); public: static Directories &get() { @@ -47,8 +59,10 @@ public: return singleton; } ~Directories(); + void open(const boost::filesystem::path &dir_path=""); void update(); + void on_save_file(boost::filesystem::path file_path); void select(const boost::filesystem::path &path); std::function on_row_activated; @@ -58,14 +72,14 @@ protected: bool on_button_press_event(GdkEventButton *event) override; private: - void add_path(const boost::filesystem::path &dir_path, const Gtk::TreeModel::Row &row, time_t last_write_time=0); + void add_or_update_path(const boost::filesystem::path &dir_path, const Gtk::TreeModel::Row &row, bool include_parent_paths); + void colorize_path(const boost::filesystem::path &dir_path, bool include_parent_paths); + Glib::RefPtr tree_store; TreeStore::ColumnRecord column_record; - std::unordered_map > last_write_times; - std::mutex update_mutex; - std::thread update_thread; - std::atomic stop_update_thread; + std::unordered_map directories; + Dispatcher dispatcher; Gtk::Menu menu; diff --git a/src/files.h b/src/files.h index 5546294..ac1d96c 100644 --- a/src/files.h +++ b/src/files.h @@ -2,7 +2,7 @@ #define JUCI_FILES_H_ #include -#define JUCI_VERSION "1.1.3-5" +#define JUCI_VERSION "1.2.0-rc3" const std::string default_config_file = R"RAW({ "version": ")RAW"+std::string(JUCI_VERSION)+R"RAW(", @@ -44,6 +44,7 @@ R"RAW( "show_whitespace_characters": "", "show_map": true, "map_font_size": "1", + "show_git_diff": true, "spellcheck_language_comment": "Use \"\" to set language from your locale settings", "spellcheck_language": "en_US", "auto_tab_char_and_size_comment": "Use false to always use default tab char and size", @@ -92,6 +93,8 @@ R"RAW( "source_spellcheck": "", "source_spellcheck_clear": "", "source_spellcheck_next_error": "e", + "source_git_next_diff": "k", + "source_git_show_diff": "", "source_indentation_set_buffer_tab": "", "source_indentation_auto_indent_buffer": "i", "source_goto_line": "g", @@ -102,6 +105,7 @@ R"RAW( "source_goto_usage": "u", "source_goto_method": "m", "source_rename": "r", + "source_implement_method": "", "source_goto_next_diagnostic": "e", "source_apply_fix_its": "space", "project_set_run_arguments": "", @@ -223,6 +227,13 @@ const std::string juci_light_style = R"RAW(