Browse Source

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
merge-requests/365/head
Ole Christian Eidheim 10 years ago committed by GitHub
parent
commit
2bc5d65251
  1. 1
      CMakeLists.txt
  2. 2
      README.md
  3. 2
      ci/update_ci.sh
  4. 16
      docs/install.md
  5. 9
      src/CMakeLists.txt
  6. 7
      src/config.cc
  7. 1
      src/config.h
  8. 286
      src/directories.cc
  9. 28
      src/directories.h
  10. 27
      src/files.h
  11. 284
      src/git.cc
  12. 102
      src/git.h
  13. 20
      src/menu.cc
  14. 4
      src/notebook.cc
  15. 1
      src/selectiondialog.cc
  16. 5
      src/source.cc
  17. 5
      src/source.h
  18. 67
      src/source_clang.cc
  19. 369
      src/source_diff.cc
  20. 71
      src/source_diff.h
  21. 24
      src/source_spellcheck.cc
  22. 2
      src/source_spellcheck.h
  23. 59
      src/window.cc
  24. 1
      src/window.h
  25. 13
      tests/CMakeLists.txt
  26. 41
      tests/git_test.cc
  27. 11
      tests/stubs/directories.cc

1
CMakeLists.txt

@ -39,6 +39,7 @@ find_package(ASPELL REQUIRED)
include(FindPkgConfig) include(FindPkgConfig)
pkg_check_modules(GTKMM gtkmm-3.0 REQUIRED) pkg_check_modules(GTKMM gtkmm-3.0 REQUIRED)
pkg_check_modules(GTKSVMM gtksourceviewmm-3.0 REQUIRED) pkg_check_modules(GTKSVMM gtksourceviewmm-3.0 REQUIRED)
pkg_check_modules(LIBGIT2 libgit2 REQUIRED)
add_subdirectory("src") add_subdirectory("src")

2
README.md

@ -15,6 +15,7 @@ towards libclang with speed and ease of use in mind.
* C++ Fix-its * C++ Fix-its
* Debug integration, both local and remote, through lldb * Debug integration, both local and remote, through lldb
* Automated CMake processing, including support for external libraries * Automated CMake processing, including support for external libraries
* Git support through libgit2
* Fast C++ autocompletion * Fast C++ autocompletion
* Keyword and buffer autocompletion for other file types * Keyword and buffer autocompletion for other file types
* Tooltips showing type information and doxygen documentation (C++) * Tooltips showing type information and doxygen documentation (C++)
@ -48,6 +49,7 @@ See [enhancements](https://github.com/cppit/jucipp/labels/enhancement) for plann
* aspell * aspell
* libclang * libclang
* lldb * lldb
* libgit2
* [libclangmm](http://github.com/cppit/libclangmm/) (downloaded directly with git --recursive, no need to install) * [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) * [tiny-process-library](http://github.com/eidheim/tiny-process-library/) (downloaded directly with git --recursive, no need to install)

2
ci/update_ci.sh

@ -32,7 +32,7 @@ function windows () {
if [ "$PLATFORM" == "x86" ]; then if [ "$PLATFORM" == "x86" ]; then
arch=i686 arch=i686
fi 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 if [ "$TRAVIS_OS_NAME" == "" ]; then

16
docs/install.md

@ -16,7 +16,7 @@
## Debian testing/Linux Mint/Ubuntu ## Debian testing/Linux Mint/Ubuntu
Install dependencies: Install dependencies:
```sh ```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: Get juCi++ source, compile and install:
@ -32,7 +32,7 @@ sudo make install
## Debian stable/Linux Mint Debian Edition/Raspbian ## Debian stable/Linux Mint Debian Edition/Raspbian
Install dependencies: Install dependencies:
```sh ```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: Get juCi++ source, compile and install:
@ -58,7 +58,7 @@ Alternatively, follow the instructions below.
Install dependencies: Install dependencies:
```sh ```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: Get juCi++ source, compile and install:
@ -74,7 +74,7 @@ sudo make install
## Fedora 23 ## Fedora 23
Install dependencies: Install dependencies:
```sh ```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: Get juCi++ source, compile and install:
@ -96,12 +96,12 @@ Install dependencies:
32-bit: 32-bit:
```sh ```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: 64-bit:
```sh ```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: Get juCi++ source, compile and install:
@ -120,7 +120,7 @@ sudo make install
Install dependencies: Install dependencies:
```sh ```sh
brew install --with-clang --with-lldb llvm 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: Get juCi++ source, compile and install:
@ -138,7 +138,7 @@ make install
Install dependencies (replace `x86_64` with `i686` for 32-bit MSYS2 installs): Install dependencies (replace `x86_64` with `i686` for 32-bit MSYS2 installs):
```sh ```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). Note that juCi++ must be built and run in a MinGW Shell (for instance MinGW-w64 Win64 Shell).

9
src/CMakeLists.txt

@ -19,18 +19,23 @@ set(global_includes
${GTKSVMM_INCLUDE_DIRS} ${GTKSVMM_INCLUDE_DIRS}
${LIBCLANG_INCLUDE_DIRS} ${LIBCLANG_INCLUDE_DIRS}
${ASPELL_INCLUDE_DIR} ${ASPELL_INCLUDE_DIR}
${LIBGIT2_INCLUDE_DIRS}
../libclangmm/src ../libclangmm/src
../tiny-process-library ../tiny-process-library
. .
) )
set(global_libraries if(MSYS)
set(global_libraries winpthread)
endif()
set(global_libraries ${global_libraries}
${GTKMM_LIBRARIES} ${GTKMM_LIBRARIES}
${GTKSVMM_LIBRARIES} ${GTKSVMM_LIBRARIES}
${Boost_LIBRARIES} ${Boost_LIBRARIES}
${LIBCLANG_LIBRARIES} ${LIBCLANG_LIBRARIES}
${LIBLLDB_LIBRARIES} ${LIBLLDB_LIBRARIES}
${ASPELL_LIBRARIES} ${ASPELL_LIBRARIES}
${LIBGIT2_LIBRARIES}
) )
set(project_files set(project_files
@ -55,9 +60,11 @@ set(project_shared_files
cmake.cc cmake.cc
dispatcher.cc dispatcher.cc
filesystem.cc filesystem.cc
git.cc
project_build.cc project_build.cc
source.cc source.cc
source_clang.cc source_clang.cc
source_diff.cc
source_spellcheck.cc source_spellcheck.cc
../libclangmm/src/CodeCompleteResults.cc ../libclangmm/src/CodeCompleteResults.cc

7
src/config.cc

@ -163,6 +163,11 @@ void Config::update_config_file() {
cfg_ok=false; cfg_ok=false;
if(cfg.count("version")>0) if(cfg.count("version")>0)
cfg.find("version")->second.data()=default_cfg.get<std::string>("version"); cfg.find("version")->second.data()=default_cfg.get<std::string>("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 else
return; return;
@ -189,6 +194,8 @@ void Config::get_source() {
source.show_map = source_json.get<bool>("show_map"); source.show_map = source_json.get<bool>("show_map");
source.map_font_size = source_json.get<std::string>("map_font_size"); source.map_font_size = source_json.get<std::string>("map_font_size");
source.show_git_diff = source_json.get<bool>("show_git_diff");
source.spellcheck_language = source_json.get<std::string>("spellcheck_language"); source.spellcheck_language = source_json.get<std::string>("spellcheck_language");
source.default_tab_char = source_json.get<char>("default_tab_char"); source.default_tab_char = source_json.get<char>("default_tab_char");

1
src/config.h

@ -60,6 +60,7 @@ public:
bool show_map; bool show_map;
std::string map_font_size; std::string map_font_size;
bool show_git_diff;
bool auto_tab_char_and_size; bool auto_tab_char_and_size;
char default_tab_char; char default_tab_char;

286
src/directories.cc

@ -1,6 +1,5 @@
#include "directories.h" #include "directories.h"
#include <algorithm> #include <algorithm>
#include <unordered_set>
#include "source.h" #include "source.h"
#include "terminal.h" #include "terminal.h"
#include "notebook.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; auto new_file_path=target_path;
for(;file_it!=view->file_path.end();file_it++) for(;file_it!=view->file_path.end();file_it++)
new_file_path/=*file_it; new_file_path/=*file_it;
view->file_path=new_file_path; {
std::unique_lock<std::mutex> lock(view->file_path_mutex);
view->file_path=new_file_path;
}
g_signal_emit_by_name(view->get_buffer()->gobj(), "modified_changed"); g_signal_emit_by_name(view->get_buffer()->gobj(), "modified_changed");
} }
} }
else if(view->file_path==source_path) { else if(view->file_path==source_path) {
view->file_path=target_path; {
std::unique_lock<std::mutex> lock(view->file_path_mutex);
view->file_path=target_path;
}
g_signal_emit_by_name(view->get_buffer()->gobj(), "modified_changed"); g_signal_emit_by_name(view->get_buffer()->gobj(), "modified_changed");
break; break;
} }
@ -118,7 +123,7 @@ bool Directories::TreeStore::drag_data_delete_vfunc (const Gtk::TreeModel::Path
return false; return false;
} }
Directories::Directories() : Gtk::TreeView(), stop_update_thread(false) { Directories::Directories() : Gtk::TreeView() {
this->set_enable_tree_lines(true); this->set_enable_tree_lines(true);
tree_store = TreeStore::create(); 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){ 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)=="") { if(iter->children().begin()->get_value(column_record.path)=="")
std::unique_lock<std::mutex> lock(update_mutex); add_or_update_path(iter->get_value(column_record.path), *iter, true);
add_path(iter->get_value(column_record.path), *iter);
}
return false; return false;
}); });
signal_row_collapsed().connect([this](const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path){ signal_row_collapsed().connect([this](const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path){
{ auto directory_str=iter->get_value(column_record.path).string();
std::unique_lock<std::mutex> lock(update_mutex); for(auto it=directories.begin();it!=directories.end();) {
auto directory_str=iter->get_value(column_record.path).string(); if(directory_str==it->first.substr(0, directory_str.size()))
for(auto it=last_write_times.begin();it!=last_write_times.end();) { it=directories.erase(it);
if(directory_str==it->first.substr(0, directory_str.size())) else
it=last_write_times.erase(it); it++;
else
it++;
}
} }
auto children=iter->children(); auto children=iter->children();
if(children) { if(children) {
@ -172,35 +172,7 @@ Directories::Directories() : Gtk::TreeView(), stop_update_thread(false) {
} }
auto child=tree_store->append(iter->children()); auto child=tree_store->append(iter->children());
child->set_value(column_record.name, std::string("(empty)")); child->set_value(column_record.name, std::string("(empty)"));
Gdk::RGBA rgba; child->set_value(column_record.type, PathType::UNKNOWN);
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<std::mutex> 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<last_write_time) {
auto path=std::make_shared<std::string>(it->first);
dispatcher.post([this, path, last_write_time] {
std::unique_lock<std::mutex> 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);
}
} }
}); });
@ -330,12 +302,18 @@ Directories::Directories() : Gtk::TreeView(), stop_update_thread(false) {
auto new_file_path=target_path; auto new_file_path=target_path;
for(;file_it!=view->file_path.end();file_it++) for(;file_it!=view->file_path.end();file_it++)
new_file_path/=*file_it; new_file_path/=*file_it;
view->file_path=new_file_path; {
std::unique_lock<std::mutex> lock(view->file_path_mutex);
view->file_path=new_file_path;
}
g_signal_emit_by_name(view->get_buffer()->gobj(), "modified_changed"); g_signal_emit_by_name(view->get_buffer()->gobj(), "modified_changed");
} }
} }
else if(view->file_path==*source_path) { else if(view->file_path==*source_path) {
view->file_path=target_path; {
std::unique_lock<std::mutex> lock(view->file_path_mutex);
view->file_path=target_path;
}
g_signal_emit_by_name(view->get_buffer()->gobj(), "modified_changed"); g_signal_emit_by_name(view->get_buffer()->gobj(), "modified_changed");
std::string old_language_id; std::string old_language_id;
@ -415,8 +393,6 @@ Directories::Directories() : Gtk::TreeView(), stop_update_thread(false) {
} }
Directories::~Directories() { Directories::~Directories() {
stop_update_thread=true;
update_thread.join();
dispatcher.disconnect(); dispatcher.disconnect();
} }
@ -425,11 +401,6 @@ void Directories::open(const boost::filesystem::path &dir_path) {
return; return;
tree_store->clear(); tree_store->clear();
{
std::unique_lock<std::mutex> lock(update_mutex);
last_write_times.clear();
}
//TODO: report that set_title does not handle '_' correctly? //TODO: report that set_title does not handle '_' correctly?
auto title=dir_path.filename().string(); 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); get_column(0)->set_title(title);
{ for(auto &directory: directories) {
std::unique_lock<std::mutex> lock(update_mutex); if(directory.second.repository)
add_path(dir_path, Gtk::TreeModel::Row()); directory.second.repository->clear_saved_status();
} }
directories.clear();
add_or_update_path(dir_path, Gtk::TreeModel::Row(), true);
path=dir_path; path=dir_path;
} }
void Directories::update() { void Directories::update() {
{ std::vector<std::pair<std::string, Gtk::TreeModel::Row> > saved_directories;
std::unique_lock<std::mutex> lock(update_mutex); for(auto &directory: directories)
for(auto &last_write_time: last_write_times) { saved_directories.emplace_back(directory.first, directory.second.row);
add_path(last_write_time.first, last_write_time.second.first); 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) { 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(); parent_path=select_path.parent_path();
//check if select_path is already expanded //check if select_path is already expanded
size_t expanded; if(directories.find(parent_path.string())!=directories.end()) {
{
std::unique_lock<std::mutex> lock(update_mutex);
expanded=last_write_times.find(parent_path.string())!=last_write_times.end();
}
if(expanded) {
//set cursor at select_path and return //set cursor at select_path and return
tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter){ tree_store->foreach_iter([this, &select_path](const Gtk::TreeModel::iterator &iter){
if(iter->get_value(column_record.path)==select_path) { 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) { for(auto &a_path: paths) {
tree_store->foreach_iter([this, &a_path](const Gtk::TreeModel::iterator &iter){ tree_store->foreach_iter([this, &a_path](const Gtk::TreeModel::iterator &iter){
if(iter->get_value(column_record.path)==a_path) { if(iter->get_value(column_record.path)==a_path) {
std::unique_lock<std::mutex> lock(update_mutex); add_or_update_path(a_path, *iter, true);
add_path(a_path, *iter);
return true; return true;
} }
return false; return false;
@ -557,16 +532,71 @@ bool Directories::on_button_press_event(GdkEventButton* event) {
return Gtk::TreeView::on_button_press_event(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) { void Directories::add_or_update_path(const boost::filesystem::path &dir_path, const Gtk::TreeModel::Row &row, bool include_parent_paths) {
boost::system::error_code ec; auto path_it=directories.find(dir_path.string());
if(last_write_time==0) if(!boost::filesystem::exists(dir_path)) {
last_write_time=boost::filesystem::last_write_time(dir_path, ec); if(path_it!=directories.end())
if(ec) directories.erase(path_it);
return; 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<std::pair<boost::filesystem::path, Gtk::TreeModel::Row> >(dir_path, row);
auto connection=std::make_shared<sigc::connection>();
std::shared_ptr<Git::Repository> 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<Gio::File> &file,
const Glib::RefPtr<Gio::File>&,
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<sigc::connection> repository_connection(new sigc::connection(), [](sigc::connection *connection) {
connection->disconnect();
delete connection;
});
if(repository) {
auto connection=std::make_shared<sigc::connection>();
*repository_connection=repository->monitor->signal_changed().connect([this, connection, path_and_row](const Glib::RefPtr<Gio::File> &file,
const Glib::RefPtr<Gio::File>&,
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<Gtk::TreeNodeChildren> children; //Gtk::TreeNodeChildren is missing default constructor... std::unique_ptr<Gtk::TreeNodeChildren> children; //Gtk::TreeNodeChildren is missing default constructor...
if(parent) if(row)
children=std::unique_ptr<Gtk::TreeNodeChildren>(new Gtk::TreeNodeChildren(parent.children())); children=std::unique_ptr<Gtk::TreeNodeChildren>(new Gtk::TreeNodeChildren(row.children()));
else else
children=std::unique_ptr<Gtk::TreeNodeChildren>(new Gtk::TreeNodeChildren(tree_store->children())); children=std::unique_ptr<Gtk::TreeNodeChildren>(new Gtk::TreeNodeChildren(tree_store->children()));
if(*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); child->set_value(column_record.id, "a"+filename);
auto grandchild=tree_store->append(child->children()); auto grandchild=tree_store->append(child->children());
grandchild->set_value(column_record.name, std::string("(empty)")); grandchild->set_value(column_record.name, std::string("(empty)"));
Gdk::RGBA rgba; grandchild->set_value(column_record.type, PathType::UNKNOWN);
rgba.set_rgba(0.5, 0.5, 0.5);
grandchild->set_value(column_record.color, rgba);
} }
else { else {
child->set_value(column_record.id, "b"+filename); child->set_value(column_record.id, "b"+filename);
auto language=Source::guess_language(it->path().filename()); auto language=Source::guess_language(it->path().filename());
if(!language) { if(!language)
Gdk::RGBA rgba; child->set_value(column_record.type, PathType::UNKNOWN);
rgba.set_rgba(0.5, 0.5, 0.5);
child->set_value(column_record.color, rgba);
}
} }
} }
} }
@ -624,8 +649,87 @@ void Directories::add_path(const boost::filesystem::path &dir_path, const Gtk::T
if(!*children) { if(!*children) {
auto child=tree_store->append(*children); auto child=tree_store->append(*children);
child->set_value(column_record.name, std::string("(empty)")); child->set_value(column_record.name, std::string("(empty)"));
Gdk::RGBA rgba; child->set_value(column_record.type, PathType::UNKNOWN);
rgba.set_rgba(0.5, 0.5, 0.5); }
child->set_value(column_record.color, rgba);
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<boost::filesystem::path>(dir_path_);
auto repository=it->second.repository;
std::thread git_status_thread([this, dir_path, repository, include_parent_paths] {
auto status=std::make_shared<Git::Repository::Status>();
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<Gtk::TreeNodeChildren> children; //Gtk::TreeNodeChildren is missing default constructor...
if(it->second.row)
children=std::unique_ptr<Gtk::TreeNodeChildren>(new Gtk::TreeNodeChildren(it->second.row.children()));
else
children=std::unique_ptr<Gtk::TreeNodeChildren>(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();
} }
} }

28
src/directories.h

@ -9,10 +9,21 @@
#include <mutex> #include <mutex>
#include <atomic> #include <atomic>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include "git.h"
#include "dispatcher.h" #include "dispatcher.h"
class Directories : public Gtk::TreeView { class Directories : public Gtk::TreeView {
public: class DirectoryData {
public:
Gtk::TreeModel::Row row;
Glib::RefPtr<Gio::FileMonitor> monitor;
std::shared_ptr<Git::Repository> repository;
std::shared_ptr<sigc::connection> connection;
};
enum class PathType {KNOWN, UNKNOWN};
class TreeStore : public Gtk::TreeStore { class TreeStore : public Gtk::TreeStore {
protected: protected:
TreeStore() {} TreeStore() {}
@ -28,18 +39,19 @@ public:
add(id); add(id);
add(name); add(name);
add(path); add(path);
add(type);
add(color); add(color);
} }
Gtk::TreeModelColumn<std::string> id; Gtk::TreeModelColumn<std::string> id;
Gtk::TreeModelColumn<std::string> name; Gtk::TreeModelColumn<std::string> name;
Gtk::TreeModelColumn<boost::filesystem::path> path; Gtk::TreeModelColumn<boost::filesystem::path> path;
Gtk::TreeModelColumn<PathType> type;
Gtk::TreeModelColumn<Gdk::RGBA> color; Gtk::TreeModelColumn<Gdk::RGBA> color;
}; };
static Glib::RefPtr<TreeStore> create() {return Glib::RefPtr<TreeStore>(new TreeStore());} static Glib::RefPtr<TreeStore> create() {return Glib::RefPtr<TreeStore>(new TreeStore());}
}; };
private:
Directories(); Directories();
public: public:
static Directories &get() { static Directories &get() {
@ -47,8 +59,10 @@ public:
return singleton; return singleton;
} }
~Directories(); ~Directories();
void open(const boost::filesystem::path &dir_path=""); void open(const boost::filesystem::path &dir_path="");
void update(); void update();
void on_save_file(boost::filesystem::path file_path);
void select(const boost::filesystem::path &path); void select(const boost::filesystem::path &path);
std::function<void(const boost::filesystem::path &path)> on_row_activated; std::function<void(const boost::filesystem::path &path)> on_row_activated;
@ -58,14 +72,14 @@ protected:
bool on_button_press_event(GdkEventButton *event) override; bool on_button_press_event(GdkEventButton *event) override;
private: 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<Gtk::TreeStore> tree_store; Glib::RefPtr<Gtk::TreeStore> tree_store;
TreeStore::ColumnRecord column_record; TreeStore::ColumnRecord column_record;
std::unordered_map<std::string, std::pair<Gtk::TreeModel::Row, std::time_t> > last_write_times; std::unordered_map<std::string, DirectoryData> directories;
std::mutex update_mutex;
std::thread update_thread;
std::atomic<bool> stop_update_thread;
Dispatcher dispatcher; Dispatcher dispatcher;
Gtk::Menu menu; Gtk::Menu menu;

27
src/files.h

@ -2,7 +2,7 @@
#define JUCI_FILES_H_ #define JUCI_FILES_H_
#include <string> #include <string>
#define JUCI_VERSION "1.1.3-5" #define JUCI_VERSION "1.2.0-rc3"
const std::string default_config_file = R"RAW({ const std::string default_config_file = R"RAW({
"version": ")RAW"+std::string(JUCI_VERSION)+R"RAW(", "version": ")RAW"+std::string(JUCI_VERSION)+R"RAW(",
@ -44,6 +44,7 @@ R"RAW(
"show_whitespace_characters": "", "show_whitespace_characters": "",
"show_map": true, "show_map": true,
"map_font_size": "1", "map_font_size": "1",
"show_git_diff": true,
"spellcheck_language_comment": "Use \"\" to set language from your locale settings", "spellcheck_language_comment": "Use \"\" to set language from your locale settings",
"spellcheck_language": "en_US", "spellcheck_language": "en_US",
"auto_tab_char_and_size_comment": "Use false to always use default tab char and size", "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": "",
"source_spellcheck_clear": "", "source_spellcheck_clear": "",
"source_spellcheck_next_error": "<primary><shift>e", "source_spellcheck_next_error": "<primary><shift>e",
"source_git_next_diff": "<primary>k",
"source_git_show_diff": "",
"source_indentation_set_buffer_tab": "", "source_indentation_set_buffer_tab": "",
"source_indentation_auto_indent_buffer": "<primary><shift>i", "source_indentation_auto_indent_buffer": "<primary><shift>i",
"source_goto_line": "<primary>g", "source_goto_line": "<primary>g",
@ -102,6 +105,7 @@ R"RAW(
"source_goto_usage": "<primary>u", "source_goto_usage": "<primary>u",
"source_goto_method": "<primary>m", "source_goto_method": "<primary>m",
"source_rename": "<primary>r", "source_rename": "<primary>r",
"source_implement_method": "",
"source_goto_next_diagnostic": "<primary>e", "source_goto_next_diagnostic": "<primary>e",
"source_apply_fix_its": "<control>space", "source_apply_fix_its": "<control>space",
"project_set_run_arguments": "", "project_set_run_arguments": "",
@ -223,6 +227,13 @@ const std::string juci_light_style = R"RAW(<?xml version="1.0" encoding="UTF-8"?
<style name="def:warning" foreground="orange"/> <style name="def:warning" foreground="orange"/>
<style name="def:note" foreground="black" background="light-yellow"/> <style name="def:note" foreground="black" background="light-yellow"/>
<style name="diff:added-line" foreground="green"/>
<style name="diff:removed-line" foreground="red"/>
<style name="diff:changed-line" foreground="orange"/>
<style name="diff:diff-file" use-style="def:type"/>
<style name="diff:location" use-style="def:statement"/>
<style name="diff:special-case" use-style="def:constant"/>
</style-scheme> </style-scheme>
)RAW"; )RAW";
@ -279,6 +290,13 @@ const std::string juci_dark_style = R"RAW(<?xml version="1.0" encoding="UTF-8"?>
<style name="def:warning" foreground="#FFE100"/> <style name="def:warning" foreground="#FFE100"/>
<style name="def:note" foreground="white" background="#444444"/> <style name="def:note" foreground="white" background="#444444"/>
<style name="diff:added-line" foreground="green"/>
<style name="diff:removed-line" foreground="red"/>
<style name="diff:changed-line" foreground="orange"/>
<style name="diff:diff-file" use-style="def:type"/>
<style name="diff:location" use-style="def:statement"/>
<style name="diff:special-case" use-style="def:constant"/>
</style-scheme> </style-scheme>
)RAW"; )RAW";
@ -338,6 +356,13 @@ const std::string juci_dark_blue_style = R"RAW(<?xml version="1.0" encoding="UTF
<style name="def:warning" foreground="yellow"/> <style name="def:warning" foreground="yellow"/>
<style name="def:note" foreground="white" background="#404466"/> <style name="def:note" foreground="white" background="#404466"/>
<style name="diff:added-line" foreground="green"/>
<style name="diff:removed-line" foreground="red"/>
<style name="diff:changed-line" foreground="orange"/>
<style name="diff:diff-file" use-style="def:type"/>
<style name="diff:location" use-style="def:statement"/>
<style name="diff:special-case" use-style="def:constant"/>
</style-scheme> </style-scheme>
)RAW"; )RAW";

284
src/git.cc

@ -0,0 +1,284 @@
#include "git.h"
#include <cstring>
std::mutex Git::mutex;
std::mutex Git::repositories_mutex;
std::string Git::Error::message() noexcept {
const git_error *last_error = giterr_last();
if(last_error==NULL)
return std::string();
else
return last_error->message;
}
Git::Repository::Diff::Diff(const boost::filesystem::path &path, git_repository *repository) : repository(repository) {
blob=std::shared_ptr<git_blob>(nullptr, [](git_blob *blob) {
if(blob) git_blob_free(blob);
});
Error error;
std::lock_guard<std::mutex> lock(mutex);
auto spec="HEAD:"+path.generic_string();
error.code = git_revparse_single(reinterpret_cast<git_object**>(&blob), repository, spec.c_str());
if(error)
throw std::runtime_error(error.message());
git_diff_init_options(&options, GIT_DIFF_OPTIONS_VERSION);
options.context_lines=0;
}
//Based on https://github.com/atom/git-diff/blob/master/lib/git-diff-view.coffee
int Git::Repository::Diff::hunk_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) noexcept {
auto lines=static_cast<Lines*>(payload);
auto start=hunk->new_start-1;
auto end=hunk->new_start+hunk->new_lines-1;
if(hunk->old_lines==0 && hunk->new_lines>0)
lines->added.emplace_back(start, end);
else if(hunk->new_lines==0 && hunk->old_lines>0)
lines->removed.emplace_back(start);
else
lines->modified.emplace_back(start, end);
return 0;
}
Git::Repository::Diff::Lines Git::Repository::Diff::get_lines(const std::string &buffer) {
Lines lines;
Error error;
std::lock_guard<std::mutex> lock(mutex);
#if LIBGIT2_SOVERSION>=23
error.code=git_diff_blob_to_buffer(blob.get(), NULL, buffer.c_str(), buffer.size(), NULL, &options, NULL, NULL, hunk_cb, NULL, &lines);
#else
error.code=git_diff_blob_to_buffer(blob.get(), NULL, buffer.c_str(), buffer.size(), NULL, &options, NULL, hunk_cb, NULL, &lines);
#endif
if(error)
throw std::runtime_error(error.message());
return lines;
}
int Git::Repository::Diff::line_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload) noexcept {
auto details=static_cast<std::pair<std::string, int> *>(payload);
auto line_nr=details->second;
auto start=hunk->new_start-1;
auto end=hunk->new_start+hunk->new_lines-1;
if(line_nr==start || (line_nr>=start && line_nr<end)) {
if(details->first.empty())
details->first+=std::string(hunk->header, hunk->header_len);
details->first+=line->origin+std::string(line->content, line->content_len);
}
return 0;
}
std::string Git::Repository::Diff::get_details(const std::string &buffer, int line_nr) {
std::pair<std::string, int> details;
details.second=line_nr;
Error error;
std::lock_guard<std::mutex> lock(mutex);
#if LIBGIT2_SOVERSION>=23
error.code=git_diff_blob_to_buffer(blob.get(), NULL, buffer.c_str(), buffer.size(), NULL, &options, NULL, NULL, NULL, line_cb, &details);
#else
error.code=git_diff_blob_to_buffer(blob.get(), NULL, buffer.c_str(), buffer.size(), NULL, &options, NULL, NULL, line_cb, &details);
#endif
if(error)
throw std::runtime_error(error.message());
return details.first;
}
Git::Repository::Repository(const boost::filesystem::path &path) {
git_repository *repository_ptr;
{
Error error;
std::lock_guard<std::mutex> lock(mutex);
auto path_str=path.generic_string();
error.code = git_repository_open_ext(&repository_ptr, path_str.c_str(), 0, NULL);
if(error)
throw std::runtime_error(error.message());
}
repository=std::unique_ptr<git_repository, std::function<void(git_repository *)> >(repository_ptr, [](git_repository *ptr) {
git_repository_free(ptr);
});
work_path=get_work_path();
auto git_path_str=boost::filesystem::canonical(get_path()).string();
auto git_directory=Glib::wrap(g_file_new_for_path(git_path_str.c_str())); //TODO: report missing constructor in giomm
#if GLIB_CHECK_VERSION(2, 44, 0)
monitor=git_directory->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_WATCH_MOVES);
#else
monitor=git_directory->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_SEND_MOVED);
#endif
monitor_changed_connection=monitor->signal_changed().connect([this](const Glib::RefPtr<Gio::File> &file,
const Glib::RefPtr<Gio::File>&,
Gio::FileMonitorEvent monitor_event) {
if(monitor_event!=Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) {
this->clear_saved_status();
}
}, false);
}
Git::Repository::~Repository() {
monitor_changed_connection.disconnect();
}
std::string Git::Repository::status_string(STATUS status) noexcept {
switch(status) {
case STATUS::CURRENT: return "current";
case STATUS::NEW: return "new";
case STATUS::MODIFIED: return "modified";
case STATUS::DELETED: return "deleted";
case STATUS::RENAMED: return "renamed";
case STATUS::TYPECHANGE: return "typechange";
case STATUS::UNREADABLE: return "unreadable";
case STATUS::IGNORED: return "ignored";
case STATUS::CONFLICTED: return "conflicted";
default: return "";
}
}
int Git::Repository::status_callback(const char *path, unsigned int status_flags, void *data) noexcept {
auto callback=static_cast<std::function<void(const char *path, STATUS status)>*>(data);
STATUS status;
if((status_flags&(GIT_STATUS_INDEX_NEW|GIT_STATUS_WT_NEW))>0)
status=STATUS::NEW;
else if((status_flags&(GIT_STATUS_INDEX_MODIFIED|GIT_STATUS_WT_MODIFIED))>0)
status=STATUS::MODIFIED;
else if((status_flags&(GIT_STATUS_INDEX_DELETED|GIT_STATUS_WT_DELETED))>0)
status=STATUS::DELETED;
else if((status_flags&(GIT_STATUS_INDEX_RENAMED|GIT_STATUS_WT_RENAMED))>0)
status=STATUS::RENAMED;
else if((status_flags&(GIT_STATUS_INDEX_TYPECHANGE|GIT_STATUS_WT_TYPECHANGE))>0)
status=STATUS::TYPECHANGE;
#if LIBGIT2_SOVERSION>=23
else if((status_flags&(GIT_STATUS_WT_UNREADABLE))>0)
status=STATUS::UNREADABLE;
#endif
else if((status_flags&(GIT_STATUS_IGNORED))>0)
status=STATUS::IGNORED;
#if LIBGIT2_SOVERSION>=23
else if((status_flags&(GIT_STATUS_CONFLICTED))>0)
status=STATUS::CONFLICTED;
#endif
else
status=STATUS::CURRENT;
if(*callback)
(*callback)(path, status);
return 0;
}
Git::Repository::Status Git::Repository::get_status() {
{
std::unique_lock<std::mutex> lock(saved_status_mutex);
if(has_saved_status)
return saved_status;
}
Status status;
bool first=true;
std::unique_lock<std::mutex> status_saved_lock(saved_status_mutex, std::defer_lock);
std::function<void(const char *path, STATUS status)> callback=[this, &status, &first, &status_saved_lock](const char *path_cstr, Git::Repository::STATUS status_enum) {
if(first) {
status_saved_lock.lock();
first=false;
}
boost::filesystem::path rel_path(path_cstr);
do {
if(status_enum==Git::Repository::STATUS::MODIFIED)
status.modified.emplace((work_path/rel_path).generic_string());
if(status_enum==Git::Repository::STATUS::NEW)
status.added.emplace((work_path/rel_path).generic_string());
rel_path=rel_path.parent_path();
} while(!rel_path.empty());
};
Error error;
std::lock_guard<std::mutex> lock(mutex);
error.code = git_status_foreach(repository.get(), Repository::status_callback, &callback);
if(error)
throw std::runtime_error(error.message());
saved_status=status;
has_saved_status=true;
if(status_saved_lock)
status_saved_lock.unlock();
return status;
}
void Git::Repository::clear_saved_status() {
std::unique_lock<std::mutex> lock(saved_status_mutex);
saved_status.added.clear();
saved_status.modified.clear();
has_saved_status=false;
}
boost::filesystem::path Git::Repository::get_work_path() noexcept {
std::lock_guard<std::mutex> lock(mutex);
return Git::path(git_repository_workdir(repository.get()));
}
boost::filesystem::path Git::Repository::get_path() noexcept {
std::lock_guard<std::mutex> lock(mutex);
return Git::path(git_repository_path(repository.get()));
}
boost::filesystem::path Git::Repository::root_path(const boost::filesystem::path &path) {
git_buf root = {0, 0, 0};
{
Error error;
std::lock_guard<std::mutex> lock(mutex);
auto path_str=path.generic_string();
error.code = git_repository_discover(&root, path_str.c_str(), 0, NULL);
if(error)
throw std::runtime_error(error.message());
}
auto root_path=Git::path(root.ptr, root.size);
git_buf_free(&root);
return root_path;
}
Git::Repository::Diff Git::Repository::get_diff(const boost::filesystem::path &path) {
return Diff(path, repository.get());
}
Git::Git() : repositories(new std::unordered_map<std::string, std::pair<std::unique_ptr<Repository>, size_t> >()) {
if(!initialized) {
#if LIBGIT2_SOVERSION>=22
git_libgit2_init();
#else
git_threads_init();
#endif
initialized=true;
}
}
std::shared_ptr<Git::Repository> Git::get_repository(const boost::filesystem::path &path) {
std::lock_guard<std::mutex> lock(repositories_mutex);
auto root_path=std::make_shared<std::string>(Repository::root_path(path).generic_string());
auto it=repositories->find(*root_path);
Repository *repository_ptr;
if(it!=repositories->end()) {
it->second.second++;
repository_ptr=it->second.first.get();
}
else {
it=repositories->emplace(*root_path, std::pair<std::unique_ptr<Repository>, size_t>(std::unique_ptr<Repository>(new Repository(*root_path)), 1)).first;
repository_ptr=it->second.first.get();
}
return std::shared_ptr<Repository>(repository_ptr, [this, root_path](Repository *) {
std::lock_guard<std::mutex> lock(repositories_mutex);
auto it=repositories->find(*root_path);
it->second.second--;
if(it->second.second==0)
repositories->erase(it);
});
}
boost::filesystem::path Git::path(const char *cpath, size_t cpath_length) noexcept {
if(cpath_length==static_cast<size_t>(-1))
cpath_length=strlen(cpath);
if(cpath_length>0 && (cpath[cpath_length-1]=='/' || cpath[cpath_length-1]=='\\'))
return std::string(cpath, cpath_length-1);
else
return std::string(cpath, cpath_length);
}

102
src/git.h

@ -0,0 +1,102 @@
#ifndef JUCI_GIT_H_
#define JUCI_GIT_H_
#include <git2.h>
#include <mutex>
#include <memory>
#include <iostream>
#include <unordered_set>
#include <unordered_map>
#include <vector>
#include <giomm.h>
#include <boost/filesystem.hpp>
class Git {
public:
class Error {
friend class Git;
std::string message() noexcept;
public:
int code=0;
Error() {}
operator bool() noexcept {return code<0;}
};
class Repository {
public:
class Diff {
public:
class Lines {
public:
std::vector<std::pair<int, int> > added;
std::vector<std::pair<int, int> > modified;
std::vector<int> removed;
};
private:
friend class Repository;
Diff(const boost::filesystem::path &path, git_repository *repository);
git_repository *repository;
std::shared_ptr<git_blob> blob;
git_diff_options options;
static int hunk_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) noexcept;
static int line_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload) noexcept;
public:
Diff() : repository(nullptr), blob(nullptr) {}
Lines get_lines(const std::string &buffer);
std::string get_details(const std::string &buffer, int line_nr);
};
enum class STATUS {CURRENT, NEW, MODIFIED, DELETED, RENAMED, TYPECHANGE, UNREADABLE, IGNORED, CONFLICTED};
class Status {
public:
std::unordered_set<std::string> added;
std::unordered_set<std::string> modified;
};
private:
friend class Git;
Repository(const boost::filesystem::path &path);
static int status_callback(const char *path, unsigned int status_flags, void *data) noexcept;
std::unique_ptr<git_repository, std::function<void(git_repository *)> > repository;
boost::filesystem::path work_path;
sigc::connection monitor_changed_connection;
Status saved_status;
bool has_saved_status=false;
std::mutex saved_status_mutex;
public:
~Repository();
static std::string status_string(STATUS status) noexcept;
Status get_status();
void clear_saved_status();
boost::filesystem::path get_work_path() noexcept;
boost::filesystem::path get_path() noexcept;
static boost::filesystem::path root_path(const boost::filesystem::path &path);
Diff get_diff(const boost::filesystem::path &path);
Glib::RefPtr<Gio::FileMonitor> monitor;
};
private:
///Mutex for thread safe operations
static std::mutex mutex;
bool initialized=false;
std::unordered_map<std::string, std::pair<std::unique_ptr<Repository>, size_t> > *repositories; //Freed by OS at program exit
static std::mutex repositories_mutex;
Git();
static boost::filesystem::path path(const char *cpath, size_t cpath_length=static_cast<size_t>(-1)) noexcept;
public:
static Git &get() noexcept {
static Git instance;
return instance;
}
std::shared_ptr<Repository> get_repository(const boost::filesystem::path &path);
};
#endif //JUCI_GIT_H_

20
src/menu.cc

@ -174,6 +174,21 @@ Menu::Menu() {
" </section>" " </section>"
" <section>" " <section>"
" <submenu>" " <submenu>"
" <attribute name='label' translatable='yes'>_Git</attribute>"
" <item>"
" <attribute name='label' translatable='yes'>_Go _to _Next _Diff</attribute>"
" <attribute name='action'>app.source_git_next_diff</attribute>"
+accels["source_git_next_diff"]+ //For Ubuntu...
" </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Show _Diff</attribute>"
" <attribute name='action'>app.source_git_show_diff</attribute>"
+accels["source_git_show_diff"]+ //For Ubuntu...
" </item>"
" </submenu>"
" </section>"
" <section>"
" <submenu>"
" <attribute name='label' translatable='yes'>_Indentation</attribute>" " <attribute name='label' translatable='yes'>_Indentation</attribute>"
" <item>" " <item>"
" <attribute name='label' translatable='yes'>_Set _Current _Buffer _Tab</attribute>" " <attribute name='label' translatable='yes'>_Set _Current _Buffer _Tab</attribute>"
@ -232,6 +247,11 @@ Menu::Menu() {
" <attribute name='action'>app.source_rename</attribute>" " <attribute name='action'>app.source_rename</attribute>"
+accels["source_rename"]+ //For Ubuntu... +accels["source_rename"]+ //For Ubuntu...
" </item>" " </item>"
" <item>"
" <attribute name='label' translatable='yes'>_Implement _Method</attribute>"
" <attribute name='action'>app.source_implement_method</attribute>"
+accels["source_implement_method"]+ //For Ubuntu...
" </item>"
" </section>" " </section>"
" <section>" " <section>"
" <item>" " <item>"

4
src/notebook.cc

@ -322,6 +322,8 @@ bool Notebook::close(size_t index) {
size_t notebook_index=notebook_page.first==0?1:0; size_t notebook_index=notebook_page.first==0?1:0;
if(notebooks[notebook_index].get_n_pages()>0) if(notebooks[notebook_index].get_n_pages()>0)
focus_view(get_view(notebook_index, notebooks[notebook_index].get_current_page())); focus_view(get_view(notebook_index, notebooks[notebook_index].get_current_page()));
else
set_current_view(nullptr);
} }
} }
} }
@ -450,7 +452,7 @@ void Notebook::set_current_view(Source::View *view) {
view->hide_dialogs(); view->hide_dialogs();
} }
current_view=view; current_view=view;
if(on_change_page) if(view && on_change_page)
on_change_page(view); on_change_page(view);
} }
} }

1
src/selectiondialog.cc

@ -114,6 +114,7 @@ void SelectionDialogBase::add_row(const std::string& row) {
void SelectionDialogBase::show() { void SelectionDialogBase::show() {
shown=true; shown=true;
window.show_all(); window.show_all();
text_view.grab_focus();
if(list_view_text.get_model()->children().size()>0) { if(list_view_text.get_model()->children().size()>0) {
if(!list_view_text.get_selection()->get_selected()) { if(!list_view_text.get_selection()->get_selected()) {

5
src/source.cc

@ -3,6 +3,7 @@
#include "filesystem.h" #include "filesystem.h"
#include "terminal.h" #include "terminal.h"
#include "info.h" #include "info.h"
#include "directories.h"
#include <gtksourceview/gtksource.h> #include <gtksourceview/gtksource.h>
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
#include <boost/spirit/home/qi/char.hpp> #include <boost/spirit/home/qi/char.hpp>
@ -88,7 +89,7 @@ const REGEX_NS::regex Source::View::bracket_regex("^([ \\t]*).*\\{ *$");
const REGEX_NS::regex Source::View::no_bracket_statement_regex("^([ \\t]*)(if|for|else if|while) *\\(.*[^;}] *$"); const REGEX_NS::regex Source::View::no_bracket_statement_regex("^([ \\t]*)(if|for|else if|while) *\\(.*[^;}] *$");
const REGEX_NS::regex Source::View::no_bracket_no_para_statement_regex("^([ \\t]*)(else) *$"); const REGEX_NS::regex Source::View::no_bracket_no_para_statement_regex("^([ \\t]*)(else) *$");
Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language): SpellCheckView(), file_path(file_path), language(language) { Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language): Gsv::View(), SpellCheckView(), DiffView(file_path), language(language) {
get_source_buffer()->begin_not_undoable_action(); get_source_buffer()->begin_not_undoable_action();
last_read_time=std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); last_read_time=std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
if(language) { if(language) {
@ -293,6 +294,7 @@ bool Source::View::save(const std::vector<Source::View*> &views) {
if(filesystem::write(file_path, get_buffer())) { if(filesystem::write(file_path, get_buffer())) {
last_read_time=std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); last_read_time=std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
get_buffer()->set_modified(false); get_buffer()->set_modified(false);
Directories::get().on_save_file(file_path);
return true; return true;
} }
else { else {
@ -303,6 +305,7 @@ bool Source::View::save(const std::vector<Source::View*> &views) {
void Source::View::configure() { void Source::View::configure() {
SpellCheckView::configure(); SpellCheckView::configure();
DiffView::configure();
//TODO: Move this to notebook? Might take up too much memory doing this for every tab. //TODO: Move this to notebook? Might take up too much memory doing this for every tab.
auto style_scheme_manager=Gsv::StyleSchemeManager::get_default(); auto style_scheme_manager=Gsv::StyleSchemeManager::get_default();

5
src/source.h

@ -1,6 +1,7 @@
#ifndef JUCI_SOURCE_H_ #ifndef JUCI_SOURCE_H_
#define JUCI_SOURCE_H_ #define JUCI_SOURCE_H_
#include "source_spellcheck.h" #include "source_spellcheck.h"
#include "source_diff.h"
#include <boost/property_tree/xml_parser.hpp> #include <boost/property_tree/xml_parser.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <string> #include <string>
@ -49,7 +50,7 @@ namespace Source {
std::pair<Offset, Offset> offsets; std::pair<Offset, Offset> offsets;
}; };
class View : public SpellCheckView { class View : public SpellCheckView, public DiffView {
public: public:
View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language);
~View(); ~View();
@ -67,13 +68,13 @@ namespace Source {
void paste(); void paste();
boost::filesystem::path file_path;
Glib::RefPtr<Gsv::Language> language; Glib::RefPtr<Gsv::Language> language;
std::function<void()> auto_indent; std::function<void()> auto_indent;
std::function<Offset(const std::vector<Source::View*> &views)> get_declaration_location; std::function<Offset(const std::vector<Source::View*> &views)> get_declaration_location;
std::function<Offset(const std::vector<Source::View*> &views)> get_implementation_location; std::function<Offset(const std::vector<Source::View*> &views)> get_implementation_location;
std::function<std::vector<std::pair<Offset, std::string> >(const std::vector<Source::View*> &views)> get_usages; std::function<std::vector<std::pair<Offset, std::string> >(const std::vector<Source::View*> &views)> get_usages;
std::function<std::string()> get_method;
std::function<std::vector<std::pair<Offset, std::string> >()> get_methods; std::function<std::vector<std::pair<Offset, std::string> >()> get_methods;
std::function<std::vector<std::string>()> get_token_data; std::function<std::vector<std::string>()> get_token_data;
std::function<std::string()> get_token_spelling; std::function<std::string()> get_token_spelling;

67
src/source_clang.cc

@ -1052,6 +1052,73 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file
return usages; return usages;
}; };
get_method=[this] {
if(!parsed) {
Info::get().print("Buffer is parsing");
return std::string();
}
auto iter=get_buffer()->get_insert()->get_iter();
auto line=static_cast<unsigned>(iter.get_line());
auto index=static_cast<unsigned>(iter.get_line_index());
for(auto &token: *clang_tokens) {
auto cursor=token.get_cursor();
if(token.get_kind()==clang::TokenKind::Token_Identifier && cursor.has_type()) {
if(line==token.offsets.first.line-1 && index>=token.offsets.first.index-1 && index <=token.offsets.second.index-1) {
auto kind=cursor.get_kind();
if(kind==clang::CursorKind::CXXMethod || kind==clang::CursorKind::Constructor || kind==clang::CursorKind::Destructor) {
auto referenced=cursor.get_referenced();
if(referenced && referenced==cursor) {
std::string result;
std::string specifier;
if(kind==clang::CursorKind::CXXMethod) {
auto type=clang_getResultType(clang_getCursorType(cursor.cx_cursor));
result=clang::to_string(clang_getTypeSpelling(type));
if(!result.empty() && result.back()!='*' && result.back()!='&')
result+=' ';
if(clang_CXXMethod_isConst(cursor.cx_cursor))
specifier=" const";
}
auto name=cursor.get_spelling();
auto parent=cursor.get_semantic_parent();
std::vector<std::string> semantic_parents;
while(parent && parent.get_kind()!=clang::CursorKind::TranslationUnit) {
auto spelling=parent.get_spelling()+"::";
semantic_parents.emplace_back(spelling);
name.insert(0, spelling);
parent=parent.get_semantic_parent();
}
std::string arguments;
auto arg_size=clang_Cursor_getNumArguments(cursor.cx_cursor);
for(int c=0;c<arg_size;c++) {
auto argument_cursor=clang::Cursor(clang_Cursor_getArgument(cursor.cx_cursor, c));
auto type=clang_getCursorType(argument_cursor.cx_cursor);
auto argument_type=clang::to_string(clang_getTypeSpelling(type));
for(auto it=semantic_parents.rbegin();it!=semantic_parents.rend();++it) {
size_t pos=argument_type.find(' '+*it);
if(pos!=std::string::npos)
argument_type.erase(pos+1, it->size());
}
auto argument=argument_cursor.get_spelling();
if(!arguments.empty())
arguments+=", ";
arguments+=argument_type;
if(!arguments.empty() && arguments.back()!='*' && arguments.back()!='&')
arguments+=' ';
arguments+=argument;
}
return result+name+'('+arguments+")"+specifier+" {}";
}
}
}
}
}
return std::string();
};
get_methods=[this](){ get_methods=[this](){
std::vector<std::pair<Offset, std::string> > methods; std::vector<std::pair<Offset, std::string> > methods;
if(!parsed) { if(!parsed) {

369
src/source_diff.cc

@ -0,0 +1,369 @@
#include "source_diff.h"
#include "config.h"
#include "terminal.h"
#include <boost/version.hpp>
namespace sigc {
#ifndef SIGC_FUNCTORS_DEDUCE_RESULT_TYPE_WITH_DECLTYPE
template <typename Functor>
struct functor_trait<Functor, false> {
typedef decltype (::sigc::mem_fun(std::declval<Functor&>(),
&Functor::operator())) _intermediate;
typedef typename _intermediate::result_type result_type;
typedef Functor functor_type;
};
#else
SIGC_FUNCTORS_DEDUCE_RESULT_TYPE_WITH_DECLTYPE
#endif
}
Source::DiffView::Renderer::Renderer() : Gsv::GutterRenderer() {
set_padding(4, 0);
}
void Source::DiffView::Renderer::draw_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, const Gdk::Rectangle &background_area,
const Gdk::Rectangle &cell_area, Gtk::TextIter &start, Gtk::TextIter &end,
Gsv::GutterRendererState p6) {
if(start.has_tag(tag_added) || end.has_tag(tag_added)) {
cr->set_source_rgba(0.0, 1.0, 0.0, 0.5);
cr->rectangle(cell_area.get_x(), cell_area.get_y(), 4, cell_area.get_height());
cr->fill();
}
else if(start.has_tag(tag_modified) || end.has_tag(tag_modified)) {
cr->set_source_rgba(0.9, 0.9, 0.0, 0.75);
cr->rectangle(cell_area.get_x(), cell_area.get_y(), 4, cell_area.get_height());
cr->fill();
}
if(start.has_tag(tag_removed_below) || end.has_tag(tag_removed_below)) {
cr->set_source_rgba(0.75, 0.0, 0.0, 0.5);
cr->rectangle(cell_area.get_x()-4, cell_area.get_y()+cell_area.get_height()-2, 8, 2);
cr->fill();
}
if(start.has_tag(tag_removed_above) || end.has_tag(tag_removed_above)) {
cr->set_source_rgba(0.75, 0.0, 0.0, 0.5);
cr->rectangle(cell_area.get_x()-4, cell_area.get_y(), 8, 2);
cr->fill();
}
}
Source::DiffView::DiffView(const boost::filesystem::path &file_path) : Gsv::View(), file_path(file_path), renderer(new Renderer()) {
renderer->tag_added=get_buffer()->create_tag("git_added");
renderer->tag_modified=get_buffer()->create_tag("git_modified");
renderer->tag_removed=get_buffer()->create_tag("git_removed");
renderer->tag_removed_below=get_buffer()->create_tag();
renderer->tag_removed_above=get_buffer()->create_tag();
configure();
}
Source::DiffView::~DiffView() {
dispatcher.disconnect();
if(repository) {
get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->remove(renderer.get());
buffer_insert_connection.disconnect();
buffer_erase_connection.disconnect();
monitor_changed_connection.disconnect();
delayed_buffer_changed_connection.disconnect();
delayed_monitor_changed_connection.disconnect();
parse_stop=true;
if(parse_thread.joinable())
parse_thread.join();
}
}
void Source::DiffView::configure() {
if(Config::get().source.show_git_diff) {
if(repository)
return;
}
else if(repository) {
get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->remove(renderer.get());
buffer_insert_connection.disconnect();
buffer_erase_connection.disconnect();
monitor_changed_connection.disconnect();
delayed_buffer_changed_connection.disconnect();
delayed_monitor_changed_connection.disconnect();
parse_stop=true;
if(parse_thread.joinable())
parse_thread.join();
repository=nullptr;
diff=nullptr;
return;
}
else
return;
try {
repository=Git::get().get_repository(this->file_path.parent_path());
}
catch(const std::exception &) {
return;
}
get_gutter(Gtk::TextWindowType::TEXT_WINDOW_LEFT)->insert(renderer.get(), -40);
parse_state=ParseState::STARTING;
parse_stop=false;
monitor_changed=false;
buffer_insert_connection=get_buffer()->signal_insert().connect([this](const Gtk::TextBuffer::iterator &iter ,const Glib::ustring &text, int) {
//Do not perform git diff if no newline is added and line is already marked as added
if(!iter.starts_line() && iter.has_tag(renderer->tag_added)) {
bool newline=false;
for(auto &c: text.raw()) {
if(c=='\n') {
newline=true;
break;
}
}
if(!newline)
return;
}
//Remove tag_removed_above/below if newline is inserted
else if(!text.empty() && text[0]=='\n' && iter.has_tag(renderer->tag_removed)) {
auto start_iter=get_buffer()->get_iter_at_line(iter.get_line());
auto end_iter=get_iter_at_line_end(iter.get_line());
end_iter.forward_char();
get_buffer()->remove_tag(renderer->tag_removed_above, start_iter, end_iter);
get_buffer()->remove_tag(renderer->tag_removed_below, start_iter, end_iter);
}
parse_state=ParseState::IDLE;
delayed_buffer_changed_connection.disconnect();
delayed_buffer_changed_connection=Glib::signal_timeout().connect([this]() {
parse_state=ParseState::STARTING;
return false;
}, 250);
}, false);
buffer_erase_connection=get_buffer()->signal_erase().connect([this](const Gtk::TextBuffer::iterator &start_iter, const Gtk::TextBuffer::iterator &end_iter) {
//Do not perform git diff if start_iter and end_iter is at the same line in addition to the line is tagged added
if(start_iter.get_line()==end_iter.get_line() && start_iter.has_tag(renderer->tag_added))
return;
parse_state=ParseState::IDLE;
delayed_buffer_changed_connection.disconnect();
delayed_buffer_changed_connection=Glib::signal_timeout().connect([this]() {
parse_state=ParseState::STARTING;
return false;
}, 250);
}, false);
monitor_changed_connection=repository->monitor->signal_changed().connect([this](const Glib::RefPtr<Gio::File> &file,
const Glib::RefPtr<Gio::File>&,
Gio::FileMonitorEvent monitor_event) {
if(monitor_event!=Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) {
delayed_monitor_changed_connection.disconnect();
delayed_monitor_changed_connection=Glib::signal_timeout().connect([this]() {
monitor_changed=true;
parse_state=ParseState::STARTING;
std::unique_lock<std::mutex> lock(parse_mutex);
diff=nullptr;
return false;
}, 500);
}
});
parse_thread=std::thread([this]() {
try {
diff=get_diff();
}
catch(const std::exception &) {}
try {
while(true) {
while(!parse_stop && parse_state!=ParseState::STARTING && parse_state!=ParseState::PROCESSING)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if(parse_stop)
break;
std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock);
auto expected=ParseState::STARTING;
if(parse_state.compare_exchange_strong(expected, ParseState::PREPROCESSING)) {
dispatcher.post([this] {
auto expected=ParseState::PREPROCESSING;
std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock);
if(parse_lock.try_lock()) {
if(parse_state.compare_exchange_strong(expected, ParseState::PROCESSING))
parse_buffer=get_buffer()->get_text();
parse_lock.unlock();
}
else
parse_state.compare_exchange_strong(expected, ParseState::STARTING);
});
}
else if (parse_state==ParseState::PROCESSING && parse_lock.try_lock()) {
bool expected_monitor_changed=true;
if(monitor_changed.compare_exchange_strong(expected_monitor_changed, false)) {
try {
diff=get_diff();
}
catch(const std::exception &) {
dispatcher.post([this] {
get_buffer()->remove_tag(renderer->tag_added, get_buffer()->begin(), get_buffer()->end());
get_buffer()->remove_tag(renderer->tag_modified, get_buffer()->begin(), get_buffer()->end());
get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), get_buffer()->end());
get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), get_buffer()->end());
get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), get_buffer()->end());
renderer->queue_draw();
});
}
}
if(diff)
lines=diff->get_lines(parse_buffer.raw());
else {
lines.added.clear();
lines.modified.clear();
lines.removed.clear();
}
auto expected=ParseState::PROCESSING;
if(parse_state.compare_exchange_strong(expected, ParseState::POSTPROCESSING)) {
parse_lock.unlock();
dispatcher.post([this] {
std::unique_lock<std::mutex> parse_lock(parse_mutex, std::defer_lock);
if(parse_lock.try_lock()) {
auto expected=ParseState::POSTPROCESSING;
if(parse_state.compare_exchange_strong(expected, ParseState::IDLE))
update_lines();
}
});
}
}
}
}
catch(const std::exception &e) {
auto e_what=std::make_shared<std::string>(e.what());
dispatcher.post([this, e_what] {
get_buffer()->remove_tag(renderer->tag_added, get_buffer()->begin(), get_buffer()->end());
get_buffer()->remove_tag(renderer->tag_modified, get_buffer()->begin(), get_buffer()->end());
get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), get_buffer()->end());
get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), get_buffer()->end());
get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), get_buffer()->end());
renderer->queue_draw();
Terminal::get().print("Error (git): "+*e_what+'\n', true);
});
}
});
}
Gtk::TextIter Source::DiffView::get_iter_at_line_end(int line_nr) {
if(line_nr>=get_buffer()->get_line_count())
return get_buffer()->end();
else if(line_nr+1<get_buffer()->get_line_count()) {
auto iter=get_buffer()->get_iter_at_line(line_nr+1);
iter.backward_char();
return iter;
}
else {
auto iter=get_buffer()->get_iter_at_line(line_nr);
while(!iter.ends_line() && iter.forward_char()) {}
return iter;
}
}
void Source::DiffView::git_goto_next_diff() {
auto iter=get_buffer()->get_insert()->get_iter();
auto insert_iter=iter;
bool wrapped=false;
iter.forward_char();
while(!wrapped || iter<insert_iter) {
auto toggled_tags=iter.get_toggled_tags();
for(auto &toggled_tag: toggled_tags) {
if(toggled_tag->property_name()=="git_added" ||
toggled_tag->property_name()=="git_modified" ||
toggled_tag->property_name()=="git_removed") {
get_buffer()->place_cursor(iter);
scroll_to(get_buffer()->get_insert(), 0.0, 1.0, 0.5);
return;
}
}
iter.forward_char();
if(!wrapped && iter==get_buffer()->end()) {
iter=get_buffer()->begin();
wrapped=true;
}
}
}
std::string Source::DiffView::git_get_diff_details() {
if(!diff)
return std::string();
auto line_nr=get_buffer()->get_insert()->get_iter().get_line();
auto iter=get_buffer()->get_iter_at_line(line_nr);
if(iter.has_tag(renderer->tag_removed_above))
--line_nr;
std::unique_lock<std::mutex> lock(parse_mutex);
parse_buffer=get_buffer()->get_text();
return diff->get_details(parse_buffer.raw(), line_nr);
}
///Return repository diff instance. Throws exception on error
std::unique_ptr<Git::Repository::Diff> Source::DiffView::get_diff() {
auto work_path=boost::filesystem::canonical(repository->get_work_path());
boost::filesystem::path relative_path;
{
std::unique_lock<std::mutex> lock(file_path_mutex);
#if BOOST_VERSION>=106000
relative_path=boost::filesystem::relative(file_path, work_path);
#else
if(std::distance(file_path.begin(), file_path.end())<std::distance(work_path.begin(), work_path.end()))
throw std::runtime_error("not a relative path");
auto work_path_it=work_path.begin();
auto file_path_it=file_path.begin();
while(file_path_it!=file_path.end() && work_path_it!=work_path.end()) {
if(*file_path_it!=*work_path_it)
throw std::runtime_error("not a relative path");
++file_path_it;
++work_path_it;
}
for(;file_path_it!=file_path.end();++file_path_it)
relative_path/=*file_path_it;
#endif
}
return std::unique_ptr<Git::Repository::Diff>(new Git::Repository::Diff(repository->get_diff(relative_path)));
}
void Source::DiffView::update_lines() {
get_buffer()->remove_tag(renderer->tag_added, get_buffer()->begin(), get_buffer()->end());
get_buffer()->remove_tag(renderer->tag_modified, get_buffer()->begin(), get_buffer()->end());
get_buffer()->remove_tag(renderer->tag_removed, get_buffer()->begin(), get_buffer()->end());
get_buffer()->remove_tag(renderer->tag_removed_below, get_buffer()->begin(), get_buffer()->end());
get_buffer()->remove_tag(renderer->tag_removed_above, get_buffer()->begin(), get_buffer()->end());
for(auto &added: lines.added) {
auto start_iter=get_buffer()->get_iter_at_line(added.first);
auto end_iter=get_iter_at_line_end(added.second-1);
end_iter.forward_char();
get_buffer()->apply_tag(renderer->tag_added, start_iter, end_iter);
}
for(auto &modified: lines.modified) {
auto start_iter=get_buffer()->get_iter_at_line(modified.first);
auto end_iter=get_iter_at_line_end(modified.second-1);
end_iter.forward_char();
get_buffer()->apply_tag(renderer->tag_modified, start_iter, end_iter);
}
for(auto &line_nr: lines.removed) {
Gtk::TextIter removed_start, removed_end;
if(line_nr>=0) {
auto start_iter=get_buffer()->get_iter_at_line(line_nr);
removed_start=start_iter;
auto end_iter=get_iter_at_line_end(line_nr);
end_iter.forward_char();
removed_end=end_iter;
get_buffer()->apply_tag(renderer->tag_removed_below, start_iter, end_iter);
}
if(line_nr+1<get_buffer()->get_line_count()) {
auto start_iter=get_buffer()->get_iter_at_line(line_nr+1);
if(line_nr<0)
removed_start=start_iter;
auto end_iter=get_iter_at_line_end(line_nr+1);
end_iter.forward_char();
removed_end=end_iter;
get_buffer()->apply_tag(renderer->tag_removed_above, start_iter, end_iter);
}
get_buffer()->apply_tag(renderer->tag_removed, removed_start, removed_end);
}
renderer->queue_draw();
}

71
src/source_diff.h

@ -0,0 +1,71 @@
#ifndef JUCI_SOURCE_DIFF_H_
#define JUCI_SOURCE_DIFF_H_
#include <gtksourceviewmm.h>
#include <boost/filesystem.hpp>
#include "dispatcher.h"
#include <set>
#include <map>
#include <thread>
#include <atomic>
#include <mutex>
#include "git.h"
namespace Source {
class DiffView : virtual public Gsv::View {
enum class ParseState {IDLE, STARTING, PREPROCESSING, PROCESSING, POSTPROCESSING};
class Renderer : public Gsv::GutterRenderer {
public:
Renderer();
Glib::RefPtr<Gtk::TextTag> tag_added;
Glib::RefPtr<Gtk::TextTag> tag_modified;
Glib::RefPtr<Gtk::TextTag> tag_removed;
Glib::RefPtr<Gtk::TextTag> tag_removed_below;
Glib::RefPtr<Gtk::TextTag> tag_removed_above;
protected:
void draw_vfunc(const Cairo::RefPtr<Cairo::Context> &cr, const Gdk::Rectangle &background_area,
const Gdk::Rectangle &cell_area, Gtk::TextIter &start, Gtk::TextIter &end,
Gsv::GutterRendererState p6) override;
};
public:
DiffView(const boost::filesystem::path &file_path);
~DiffView();
virtual void configure();
Gtk::TextIter get_iter_at_line_end(int line_nr);
void git_goto_next_diff();
std::string git_get_diff_details();
boost::filesystem::path file_path;
///Only needed when using file_path in a thread, or when changing file_path
std::mutex file_path_mutex;
private:
std::unique_ptr<Renderer> renderer;
Dispatcher dispatcher;
std::shared_ptr<Git::Repository> repository;
std::unique_ptr<Git::Repository::Diff> diff;
std::unique_ptr<Git::Repository::Diff> get_diff();
std::thread parse_thread;
std::atomic<ParseState> parse_state;
std::mutex parse_mutex;
std::atomic<bool> parse_stop;
Glib::ustring parse_buffer;
sigc::connection buffer_insert_connection;
sigc::connection buffer_erase_connection;
sigc::connection monitor_changed_connection;
sigc::connection delayed_buffer_changed_connection;
sigc::connection delayed_monitor_changed_connection;
std::atomic<bool> monitor_changed;
Git::Repository::Diff::Lines lines;
void update_lines();
};
}
#endif //JUCI_SOURCE_DIFF_H_

24
src/source_spellcheck.cc

@ -283,7 +283,7 @@ void Source::SpellCheckView::goto_next_spellcheck_error() {
auto insert_iter=iter; auto insert_iter=iter;
bool wrapped=false; bool wrapped=false;
iter.forward_char(); iter.forward_char();
while(iter && (!wrapped || iter<insert_iter)) { while(!wrapped || iter<insert_iter) {
auto toggled_tags=iter.get_toggled_tags(); auto toggled_tags=iter.get_toggled_tags();
for(auto &toggled_tag: toggled_tags) { for(auto &toggled_tag: toggled_tags) {
if(toggled_tag->property_name()=="spellcheck_error") { if(toggled_tag->property_name()=="spellcheck_error") {
@ -322,18 +322,20 @@ std::pair<Gtk::TextIter, Gtk::TextIter> Source::SpellCheckView::spellcheck_get_w
} }
void Source::SpellCheckView::spellcheck_word(const Gtk::TextIter& start, const Gtk::TextIter& end) { void Source::SpellCheckView::spellcheck_word(const Gtk::TextIter& start, const Gtk::TextIter& end) {
auto spellcheck_start=start; if((end.get_offset()-start.get_offset())==2) {
auto spellcheck_end=end; auto before_end=end;
if((spellcheck_end.get_offset()-spellcheck_start.get_offset())>=2) { before_end.backward_char();
auto last_char=spellcheck_end; if(*before_end=='\'' || *start=='\'')
last_char.backward_char(); return;
if(*spellcheck_start=='\'' && *last_char=='\'') { }
spellcheck_start.forward_char(); else if((end.get_offset()-start.get_offset())==3) {
spellcheck_end.backward_char(); auto before_end=end;
} before_end.backward_char();
if(*before_end=='\'' && *start=='\'')
return;
} }
auto word=get_buffer()->get_text(spellcheck_start, spellcheck_end); auto word=get_buffer()->get_text(start, end);
if(word.size()>0) { if(word.size()>0) {
auto correct = aspell_speller_check(spellcheck_checker, word.data(), word.bytes()); auto correct = aspell_speller_check(spellcheck_checker, word.data(), word.bytes());
if(correct==0) if(correct==0)

2
src/source_spellcheck.h

@ -5,7 +5,7 @@
#include "selectiondialog.h" #include "selectiondialog.h"
namespace Source { namespace Source {
class SpellCheckView : public Gsv::View { class SpellCheckView : virtual public Gsv::View {
public: public:
SpellCheckView(); SpellCheckView();
~SpellCheckView(); ~SpellCheckView();

59
src/window.cc

@ -1,6 +1,7 @@
#include "window.h" #include "window.h"
#include "config.h" #include "config.h"
#include "menu.h" #include "menu.h"
#include "notebook.h"
#include "directories.h" #include "directories.h"
//#include "api.h" //#include "api.h"
#include "dialogs.h" #include "dialogs.h"
@ -391,6 +392,35 @@ void Window::set_menu_actions() {
view->goto_next_spellcheck_error(); view->goto_next_spellcheck_error();
}); });
menu.add_action("source_git_next_diff", [this]() {
if(auto view=Notebook::get().get_current_view())
view->git_goto_next_diff();
});
menu.add_action("source_git_show_diff", [this]() {
if(auto view=Notebook::get().get_current_view()) {
auto diff_details=view->git_get_diff_details();
if(diff_details.empty()) {
Info::get().print("Could not find any changes at the current line");
return;
}
std::string postfix;
if(diff_details.size()>2) {
size_t pos=diff_details.find("@@", 2);
if(pos!=std::string::npos)
postfix=diff_details.substr(0, pos+2);
}
Notebook::get().open(view->file_path.string()+postfix+".diff");
if(auto new_view=Notebook::get().get_current_view()) {
if(new_view->get_buffer()->get_text().empty()) {
new_view->get_source_buffer()->begin_not_undoable_action();
new_view->get_buffer()->set_text(diff_details);
new_view->get_source_buffer()->end_not_undoable_action();
new_view->get_buffer()->set_modified(false);
}
}
}
});
menu.add_action("source_indentation_set_buffer_tab", [this]() { menu.add_action("source_indentation_set_buffer_tab", [this]() {
set_tab_entry(); set_tab_entry();
}); });
@ -571,6 +601,34 @@ void Window::set_menu_actions() {
menu.add_action("source_rename", [this]() { menu.add_action("source_rename", [this]() {
rename_token_entry(); rename_token_entry();
}); });
menu.add_action("source_implement_method", [this]() {
const static std::string button_text="Insert Method Implementation";
if(auto view=Notebook::get().get_current_view()) {
if(view->get_method) {
auto iter=view->get_buffer()->get_insert()->get_iter();
if(!EntryBox::get().buttons.empty() && EntryBox::get().buttons.back().get_label()==button_text &&
iter.ends_line() && iter.starts_line()) {
EntryBox::get().buttons.back().activate();
return;
}
auto method=std::make_shared<std::string>(view->get_method());
if(method->empty())
return;
EntryBox::get().clear();
EntryBox::get().labels.emplace_back();
EntryBox::get().labels.back().set_text(*method);
EntryBox::get().buttons.emplace_back(button_text, [this, method](){
if(auto view=Notebook::get().get_current_view()) {
view->get_buffer()->insert_at_cursor(*method);
EntryBox::get().clear();
}
});
EntryBox::get().show();
}
}
});
menu.add_action("source_goto_next_diagnostic", [this]() { menu.add_action("source_goto_next_diagnostic", [this]() {
if(auto view=Notebook::get().get_current_view()) { if(auto view=Notebook::get().get_current_view()) {
@ -869,6 +927,7 @@ void Window::activate_menu_items(bool activate) {
menu.actions["source_goto_usage"]->set_enabled(activate ? static_cast<bool>(notebook.get_current_view()->get_usages) : false); menu.actions["source_goto_usage"]->set_enabled(activate ? static_cast<bool>(notebook.get_current_view()->get_usages) : false);
menu.actions["source_goto_method"]->set_enabled(activate ? static_cast<bool>(notebook.get_current_view()->get_methods) : false); menu.actions["source_goto_method"]->set_enabled(activate ? static_cast<bool>(notebook.get_current_view()->get_methods) : false);
menu.actions["source_rename"]->set_enabled(activate ? static_cast<bool>(notebook.get_current_view()->rename_similar_tokens) : false); menu.actions["source_rename"]->set_enabled(activate ? static_cast<bool>(notebook.get_current_view()->rename_similar_tokens) : false);
menu.actions["source_implement_method"]->set_enabled(activate ? static_cast<bool>(notebook.get_current_view()->get_method) : false);
menu.actions["source_goto_next_diagnostic"]->set_enabled(activate ? static_cast<bool>(notebook.get_current_view()->goto_next_diagnostic) : false); menu.actions["source_goto_next_diagnostic"]->set_enabled(activate ? static_cast<bool>(notebook.get_current_view()->goto_next_diagnostic) : false);
menu.actions["source_apply_fix_its"]->set_enabled(activate ? static_cast<bool>(notebook.get_current_view()->get_fix_its) : false); menu.actions["source_apply_fix_its"]->set_enabled(activate ? static_cast<bool>(notebook.get_current_view()->get_fix_its) : false);
#ifdef JUCI_ENABLE_DEBUG #ifdef JUCI_ENABLE_DEBUG

1
src/window.h

@ -2,7 +2,6 @@
#define JUCI_WINDOW_H_ #define JUCI_WINDOW_H_
#include <gtkmm.h> #include <gtkmm.h>
#include "notebook.h"
#include <atomic> #include <atomic>
class Window : public Gtk::ApplicationWindow { class Window : public Gtk::ApplicationWindow {

13
tests/CMakeLists.txt

@ -6,23 +6,29 @@ set(global_includes
${GTKSVMM_INCLUDE_DIRS} ${GTKSVMM_INCLUDE_DIRS}
${LIBCLANG_INCLUDE_DIRS} ${LIBCLANG_INCLUDE_DIRS}
${ASPELL_INCLUDE_DIR} ${ASPELL_INCLUDE_DIR}
${LIBGIT2_INCLUDE_DIRS}
../libclangmm/src ../libclangmm/src
../tiny-process-library ../tiny-process-library
../src ../src
) )
set(global_libraries if(MSYS)
set(global_libraries winpthread)
endif()
set(global_libraries ${global_libraries}
${GTKMM_LIBRARIES} ${GTKMM_LIBRARIES}
${GTKSVMM_LIBRARIES} ${GTKSVMM_LIBRARIES}
${Boost_LIBRARIES} ${Boost_LIBRARIES}
${LIBCLANG_LIBRARIES} ${LIBCLANG_LIBRARIES}
${LIBLLDB_LIBRARIES} ${LIBLLDB_LIBRARIES}
${ASPELL_LIBRARIES} ${ASPELL_LIBRARIES}
${LIBGIT2_LIBRARIES}
) )
set(stub_files set(stub_files
stubs/config.cc stubs/config.cc
stubs/dialogs.cc stubs/dialogs.cc
stubs/directories.cc
stubs/info.cc stubs/info.cc
stubs/selectiondialog.cc stubs/selectiondialog.cc
stubs/terminal.cc stubs/terminal.cc
@ -60,3 +66,8 @@ if(LIBLLDB_FOUND AND (NOT LIBCLANG_LIBRARIES STREQUAL "/usr/lib/llvm-3.5/lib/lib
add_test(lldb_test lldb_test) add_test(lldb_test lldb_test)
add_subdirectory("lldb_test_files") add_subdirectory("lldb_test_files")
endif() endif()
add_executable(git_test git_test.cc
$<TARGET_OBJECTS:project_shared> $<TARGET_OBJECTS:stubs>)
target_link_libraries(git_test ${global_libraries})
add_test(git_test git_test)

41
tests/git_test.cc

@ -0,0 +1,41 @@
#include <glib.h>
#include <gtkmm.h>
#include "git.h"
#include <boost/filesystem.hpp>
int main() {
auto app=Gtk::Application::create();
auto tests_path=boost::filesystem::canonical(JUCI_TESTS_PATH);
auto jucipp_path=tests_path.parent_path();
auto git_path=jucipp_path/".git";
try {
auto repository=Git::get().get_repository(tests_path);
g_assert(repository->get_path()==git_path);
g_assert(repository->get_work_path()==jucipp_path);
auto status=repository->get_status();
auto diff=repository->get_diff((boost::filesystem::path("tests")/"git_test.cc"));
auto lines=diff.get_lines("#include added\n#include <glib.h>\n#include modified\n#include \"git.h\"\n");
g_assert_cmpuint(lines.added.size(), ==, 1);
g_assert_cmpuint(lines.modified.size(), ==, 1);
g_assert_cmpuint(lines.removed.size(), ==, 1);
}
catch(const std::exception &e) {
std::cerr << e.what() << std::endl;
return 1;
}
try {
g_assert(Git::Repository::root_path(tests_path)==git_path);
}
catch(const std::exception &e) {
std::cerr << e.what() << std::endl;
return 1;
}
return 0;
}

11
tests/stubs/directories.cc

@ -0,0 +1,11 @@
#include "directories.h"
Directories::Directories() {}
Directories::~Directories() {}
void Directories::on_save_file(boost::filesystem::path file_path) {}
bool Directories::on_button_press_event(GdkEventButton *event) {
return false;
};
Loading…
Cancel
Save