Browse Source

Fixes #251: Go to Usage and Rename now includes unopened files. Also includes various other cleanups related to this work.

merge-requests/365/head
eidheim 8 years ago
parent
commit
59c08a2dd8
  1. 1
      .gitignore
  2. 6
      CMakeLists.txt
  3. 1
      README.md
  4. 2
      debian/control
  5. 2
      docs/install.md
  6. 2
      libclangmm
  7. 1
      src/CMakeLists.txt
  8. 1
      src/config.cc
  9. 2
      src/config.h
  10. 16
      src/dialogs.cc
  11. 2
      src/dialogs.h
  12. 4
      src/files.h
  13. 98
      src/filesystem.cc
  14. 8
      src/filesystem.h
  15. 2
      src/notebook.cc
  16. 5
      src/project.cc
  17. 64
      src/source.cc
  18. 15
      src/source.h
  19. 384
      src/source_clang.cc
  20. 7
      src/source_clang.h
  21. 19
      src/terminal.cc
  22. 714
      src/usages_clang.cc
  23. 151
      src/usages_clang.h
  24. 25
      src/window.cc
  25. 5
      tests/CMakeLists.txt
  26. 8
      tests/source_clang_test.cc
  27. 2
      tests/source_test.cc
  28. 2
      tests/stubs/dialogs.cc
  29. 273
      tests/usages_clang_test.cc
  30. 7
      tests/usages_clang_test_files/build/compile_commands.json
  31. 9
      tests/usages_clang_test_files/main.cpp
  32. 5
      tests/usages_clang_test_files/meson.build
  33. 13
      tests/usages_clang_test_files/test.hpp
  34. 7
      tests/usages_clang_test_files/test2.hpp

1
.gitignore vendored

@ -12,3 +12,4 @@
!debian/* !debian/*
build build
.usages_clang

6
CMakeLists.txt

@ -1,7 +1,7 @@
cmake_minimum_required (VERSION 2.8.8) cmake_minimum_required (VERSION 2.8.8)
project(juci) project(juci)
set(JUCI_VERSION "1.2.5") set(JUCI_VERSION "1.3.0-rc1")
set(CPACK_PACKAGE_NAME "jucipp") set(CPACK_PACKAGE_NAME "jucipp")
set(CPACK_PACKAGE_CONTACT "Ole Christian Eidheim <eidheim@gmail.com>") set(CPACK_PACKAGE_CONTACT "Ole Christian Eidheim <eidheim@gmail.com>")
@ -11,7 +11,7 @@ set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A lightweight, platform independent C++-IDE with support for C++11, C++14, and experimental C++17 features depending on libclang version.") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A lightweight, platform independent C++-IDE with support for C++11, C++14, and experimental C++17 features depending on libclang version.")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
set(CPACK_DEBIAN_PACKAGE_DEPENDS "cmake, make, g++, libclang-3.8-dev, liblldb-3.8-dev, clang-format, pkg-config, libboost-system-dev, libboost-filesystem-dev, libgtksourceviewmm-3.0-dev, aspell-en, libaspell-dev, libgit2-dev, exuberant-ctags") set(CPACK_DEBIAN_PACKAGE_DEPENDS "cmake, make, g++, libclang-3.8-dev, liblldb-3.8-dev, clang-format, pkg-config, libboost-system-dev, libboost-filesystem-dev, libboost-serialization-dev libgtksourceviewmm-3.0-dev, aspell-en, libaspell-dev, libgit2-dev, exuberant-ctags")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/cppit/jucipp") set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/cppit/jucipp")
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
include(CPack) include(CPack)
@ -57,7 +57,7 @@ else()
message("liblldb not found. Building juCi++ without debugging support") message("liblldb not found. Building juCi++ without debugging support")
endif() endif()
find_package(Boost 1.54 COMPONENTS system filesystem REQUIRED) find_package(Boost 1.54 COMPONENTS system filesystem serialization REQUIRED)
find_package(ASPELL REQUIRED) find_package(ASPELL REQUIRED)
include(FindPkgConfig) include(FindPkgConfig)
pkg_check_modules(GTKMM gtkmm-3.0 REQUIRED) pkg_check_modules(GTKMM gtkmm-3.0 REQUIRED)

1
README.md

@ -51,6 +51,7 @@ See [enhancements](https://github.com/cppit/jucipp/labels/enhancement) for plann
## Dependencies ## Dependencies
* boost-filesystem * boost-filesystem
* boost-serialization
* gtkmm-3.0 * gtkmm-3.0
* gtksourceviewmm-3.0 * gtksourceviewmm-3.0
* aspell * aspell

2
debian/control vendored

@ -2,7 +2,7 @@ Source: jucipp
Section: unknown Section: unknown
Priority: optional Priority: optional
Maintainer: Ole Christian Eidheim <eidheim@gmail.com> Maintainer: Ole Christian Eidheim <eidheim@gmail.com>
Build-Depends: debhelper (>= 9), cmake, make, g++, libclang-dev, liblldb-3.5-dev, clang-format-3.5, pkg-config, libboost-system-dev, libboost-filesystem-dev, libgtksourceviewmm-3.0-dev, aspell-en, libaspell-dev, libgit2-dev, exuberant-ctags Build-Depends: debhelper (>= 9), cmake, make, g++, libclang-dev, liblldb-3.5-dev, clang-format-3.5, pkg-config, libboost-system-dev, libboost-filesystem-dev, libboost-serialization-dev, libgtksourceviewmm-3.0-dev, aspell-en, libaspell-dev, libgit2-dev, exuberant-ctags
Standards-Version: 3.9.5 Standards-Version: 3.9.5
Homepage: https://github.com/cppit/jucipp Homepage: https://github.com/cppit/jucipp

2
docs/install.md

@ -17,7 +17,7 @@
## Debian/Linux Mint/Ubuntu ## Debian/Linux Mint/Ubuntu
Install dependencies: Install dependencies:
```sh ```sh
sudo apt-get install git cmake make g++ libclang-3.8-dev liblldb-3.8-dev clang-format pkg-config libboost-filesystem-dev libgtksourceviewmm-3.0-dev aspell-en libaspell-dev libgit2-dev exuberant-ctags sudo apt-get install git cmake make g++ libclang-3.8-dev liblldb-3.8-dev clang-format pkg-config libboost-filesystem-dev libboost-serialization-dev libgtksourceviewmm-3.0-dev aspell-en libaspell-dev libgit2-dev exuberant-ctags
``` ```
Get juCi++ source, compile and install: Get juCi++ source, compile and install:

2
libclangmm

@ -1 +1 @@
Subproject commit 955c113c73ecdfbf5fecc1c0b8fef537693c2f25 Subproject commit b242f8d6a73cb211ea9faa9bbfd4f444e0afbf0a

1
src/CMakeLists.txt

@ -33,6 +33,7 @@ set(project_shared_files
source_diff.cc source_diff.cc
source_spellcheck.cc source_spellcheck.cc
terminal.cc terminal.cc
usages_clang.cc
${PROJECT_SOURCE_DIR}/libclangmm/src/CodeCompleteResults.cc ${PROJECT_SOURCE_DIR}/libclangmm/src/CodeCompleteResults.cc
${PROJECT_SOURCE_DIR}/libclangmm/src/CompilationDatabase.cc ${PROJECT_SOURCE_DIR}/libclangmm/src/CompilationDatabase.cc

1
src/config.cc

@ -176,6 +176,7 @@ void Config::read(const boost::property_tree::ptree &cfg) {
catch(const std::exception &) {} catch(const std::exception &) {}
} }
source.clang_format_style = source_json.get<std::string>("clang_format_style"); source.clang_format_style = source_json.get<std::string>("clang_format_style");
source.clang_usages_threads = static_cast<unsigned>(source_json.get<int>("clang_usages_threads"));
auto pt_doc_search=cfg.get_child("documentation_searches"); auto pt_doc_search=cfg.get_child("documentation_searches");
for(auto &pt_doc_search_lang: pt_doc_search) { for(auto &pt_doc_search_lang: pt_doc_search) {
source.documentation_searches[pt_doc_search_lang.first].separator=pt_doc_search_lang.second.get<std::string>("separator"); source.documentation_searches[pt_doc_search_lang.first].separator=pt_doc_search_lang.second.get<std::string>("separator");

2
src/config.h

@ -86,8 +86,10 @@ public:
bool wrap_lines; bool wrap_lines;
bool highlight_current_line; bool highlight_current_line;
bool show_line_numbers; bool show_line_numbers;
std::unordered_map<int, std::string> clang_types; std::unordered_map<int, std::string> clang_types;
std::string clang_format_style; std::string clang_format_style;
unsigned clang_usages_threads;
std::unordered_map<std::string, DocumentationSearch> documentation_searches; std::unordered_map<std::string, DocumentationSearch> documentation_searches;
}; };

16
src/dialogs.cc

@ -1,13 +1,25 @@
#include "dialogs.h" #include "dialogs.h"
#include <cmath> #include <cmath>
Dialog::Message::Message(const std::string &text): Gtk::MessageDialog(text, false, Gtk::MessageType::MESSAGE_INFO, Gtk::ButtonsType::BUTTONS_NONE, true) { Dialog::Message::Message(const std::string &text): Gtk::Window(Gtk::WindowType::WINDOW_POPUP) {
auto g_application=g_application_get_default(); auto g_application=g_application_get_default();
auto gio_application=Glib::wrap(g_application, true); auto gio_application=Glib::wrap(g_application, true);
auto application=Glib::RefPtr<Gtk::Application>::cast_static(gio_application); auto application=Glib::RefPtr<Gtk::Application>::cast_static(gio_application);
set_transient_for(*application->get_active_window()); set_transient_for(*application->get_active_window());
set_position(Gtk::WindowPosition::WIN_POS_CENTER_ON_PARENT);
set_position(Gtk::WindowPosition::WIN_POS_CENTER_ON_PARENT);
set_modal(true);
set_type_hint(Gdk::WindowTypeHint::WINDOW_TYPE_HINT_NOTIFICATION);
property_decorated()=false;
set_skip_taskbar_hint(true);
auto box=Gtk::manage(new Gtk::Box(Gtk::Orientation::ORIENTATION_VERTICAL));
auto label=Gtk::manage(new Gtk::Label(text));
label->set_padding(10, 10);
box->pack_start(*label);
add(*box);
show_all_children();
show_now(); show_now();
while(Gtk::Main::events_pending()) while(Gtk::Main::events_pending())

2
src/dialogs.h

@ -13,7 +13,7 @@ public:
static std::string new_folder(const boost::filesystem::path &path); static std::string new_folder(const boost::filesystem::path &path);
static std::string save_file_as(const boost::filesystem::path &path); static std::string save_file_as(const boost::filesystem::path &path);
class Message : public Gtk::MessageDialog { class Message : public Gtk::Window {
public: public:
Message(const std::string &text); Message(const std::string &text);
protected: protected:

4
src/files.h

@ -76,7 +76,9 @@ R"RAW(
"705": "def:comment" "705": "def:comment"
}, },
"clang_format_style_comment": "IndentWidth, AccessModifierOffset and UseTab are set automatically. See http://clang.llvm.org/docs/ClangFormatStyleOptions.html", "clang_format_style_comment": "IndentWidth, AccessModifierOffset and UseTab are set automatically. See http://clang.llvm.org/docs/ClangFormatStyleOptions.html",
"clang_format_style": "ColumnLimit: 0, MaxEmptyLinesToKeep: 2, SpaceBeforeParens: Never, NamespaceIndentation: All, BreakBeforeBraces: Custom, BraceWrapping: {BeforeElse: true, BeforeCatch: true}" "clang_format_style": "ColumnLimit: 0, MaxEmptyLinesToKeep: 2, SpaceBeforeParens: Never, NamespaceIndentation: All, BreakBeforeBraces: Custom, BraceWrapping: {BeforeElse: true, BeforeCatch: true}",
"clang_usages_threads_comment": "The number of threads used in finding usages in unparsed files. -1 corresponds to the number of cores available, and 0 disables the search",
"clang_usages_threads": -1
}, },
"terminal": { "terminal": {
"history_size": 1000, "history_size": 1000,

98
src/filesystem.cc

@ -5,8 +5,6 @@
#include "filesystem.h" #include "filesystem.h"
const size_t buffer_size=131072;
//Only use on small files //Only use on small files
std::string filesystem::read(const std::string &path) { std::string filesystem::read(const std::string &path) {
std::stringstream ss; std::stringstream ss;
@ -18,80 +16,6 @@ std::string filesystem::read(const std::string &path) {
return ss.str(); return ss.str();
} }
int filesystem::read(const std::string &path, Glib::RefPtr<Gtk::TextBuffer> text_buffer) {
std::ifstream input(path, std::ofstream::binary);
if(input) {
//need to read the whole file to make this work...
std::stringstream ss;
ss << input.rdbuf();
Glib::ustring ustr=ss.str();
bool valid=true;
if(ustr.validate())
text_buffer->insert_at_cursor(ustr);
else
valid=false;
//TODO: maybe correct this, emphasis on maybe:
/*std::vector<char> buffer(buffer_size);
size_t read_length;
while((read_length=input.read(&buffer[0], buffer_size).gcount())>0) {
//auto ustr=Glib::ustring(std::string(&buffer[0], read_length)); //this works...
//this does not work:
Glib::ustring ustr;
ustr.resize(read_length);
ustr.replace(0, read_length, &buffer[0]);
Glib::ustring::iterator iter;
while(!ustr.validate(iter)) {
auto next_char_iter=iter;
next_char_iter++;
ustr.replace(iter, next_char_iter, "?");
}
text_buffer->insert_at_cursor(ustr); //What if insert happens in the middle of an UTF-8 char???
}*/
input.close();
if(valid)
return 1;
else
return -1;
}
return 0;
}
int filesystem::read_non_utf8(const std::string &path, Glib::RefPtr<Gtk::TextBuffer> text_buffer) {
std::ifstream input(path, std::ofstream::binary);
if(input) {
//need to read the whole file to make this work...
std::stringstream ss;
ss << input.rdbuf();
Glib::ustring ustr=ss.str();
bool valid=true;
Glib::ustring::iterator iter;
while(!ustr.validate(iter)) {
auto next_char_iter=iter;
next_char_iter++;
ustr.replace(iter, next_char_iter, "?");
valid=false;
}
text_buffer->insert_at_cursor(ustr);
input.close();
if(valid)
return 1;
else
return -1;
}
return 0;
}
//Only use on small files //Only use on small files
std::vector<std::string> filesystem::read_lines(const std::string &path) { std::vector<std::string> filesystem::read_lines(const std::string &path) {
std::vector<std::string> res; std::vector<std::string> res;
@ -114,28 +38,6 @@ bool filesystem::write(const std::string &path, const std::string &new_content)
return true; return true;
} }
bool filesystem::write(const std::string &path, Glib::RefPtr<Gtk::TextBuffer> buffer) {
std::ofstream output(path, std::ofstream::binary);
if(output) {
auto start_iter=buffer->begin();
auto end_iter=start_iter;
bool end_reached=false;
while(!end_reached) {
for(size_t c=0;c<buffer_size;c++) {
if(!end_iter.forward_char()) {
end_reached=true;
break;
}
}
output << buffer->get_text(start_iter, end_iter).c_str();
start_iter=end_iter;
}
output.close();
return true;
}
return false;
}
std::string filesystem::escape_argument(const std::string &argument) { std::string filesystem::escape_argument(const std::string &argument) {
auto escaped=argument; auto escaped=argument;
for(size_t pos=0;pos<escaped.size();++pos) { for(size_t pos=0;pos<escaped.size();++pos) {

8
src/filesystem.h

@ -3,17 +3,11 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <gtkmm.h>
class filesystem { class filesystem {
public: public:
static std::string read(const std::string &path); static std::string read(const std::string &path);
static std::string read(const boost::filesystem::path &path) { return read(path.string()); } static std::string read(const boost::filesystem::path &path) { return read(path.string()); }
static int read(const std::string &path, Glib::RefPtr<Gtk::TextBuffer> text_buffer);
static int read(const boost::filesystem::path &path, Glib::RefPtr<Gtk::TextBuffer> text_buffer) { return read(path.string(), text_buffer); }
static int read_non_utf8(const std::string &path, Glib::RefPtr<Gtk::TextBuffer> text_buffer);
static int read_non_utf8(const boost::filesystem::path &path, Glib::RefPtr<Gtk::TextBuffer> text_buffer) { return read_non_utf8(path.string(), text_buffer); }
static std::vector<std::string> read_lines(const std::string &path); static std::vector<std::string> read_lines(const std::string &path);
static std::vector<std::string> read_lines(const boost::filesystem::path &path) { return read_lines(path.string()); }; static std::vector<std::string> read_lines(const boost::filesystem::path &path) { return read_lines(path.string()); };
@ -22,8 +16,6 @@ public:
static bool write(const boost::filesystem::path &path, const std::string &new_content) { return write(path.string(), new_content); } static bool write(const boost::filesystem::path &path, const std::string &new_content) { return write(path.string(), new_content); }
static bool write(const std::string &path) { return write(path, ""); }; static bool write(const std::string &path) { return write(path, ""); };
static bool write(const boost::filesystem::path &path) { return write(path, ""); }; static bool write(const boost::filesystem::path &path) { return write(path, ""); };
static bool write(const std::string &path, Glib::RefPtr<Gtk::TextBuffer> text_buffer);
static bool write(const boost::filesystem::path &path, Glib::RefPtr<Gtk::TextBuffer> text_buffer) { return write(path.string(), text_buffer); }
static std::string escape_argument(const std::string &argument); static std::string escape_argument(const std::string &argument);
static std::string unescape_argument(const std::string &argument); static std::string unescape_argument(const std::string &argument);

2
src/notebook.cc

@ -422,7 +422,7 @@ void Notebook::configure(size_t index) {
} }
bool Notebook::save(size_t index) { bool Notebook::save(size_t index) {
if(!source_views[index]->save(source_views)) if(!source_views[index]->save())
return false; return false;
Project::on_save(index); Project::on_save(index);
return true; return true;

5
src/project.cc

@ -11,6 +11,7 @@
#include "debug_lldb.h" #include "debug_lldb.h"
#endif #endif
#include "info.h" #include "info.h"
#include "usages_clang.h"
boost::filesystem::path Project::debug_last_stop_file_path; boost::filesystem::path Project::debug_last_stop_file_path;
std::unordered_map<std::string, std::string> Project::run_arguments; std::unordered_map<std::string, std::string> Project::run_arguments;
@ -61,7 +62,9 @@ void Project::on_save(size_t index) {
auto build=Build::create(build_path); auto build=Build::create(build_path);
if(dynamic_cast<CMakeBuild*>(build.get()) || dynamic_cast<MesonBuild*>(build.get())) { if(dynamic_cast<CMakeBuild*>(build.get()) || dynamic_cast<MesonBuild*>(build.get())) {
build->update_default(true); build->update_default(true);
if(boost::filesystem::exists(build->get_debug_path())) Usages::Clang::erase_all_caches_for_project(build->project_path, build->get_default_path());
boost::system::error_code ec;
if(boost::filesystem::exists(build->get_debug_path()), ec)
build->update_debug(true); build->update_debug(true);
for(size_t c=0;c<Notebook::get().size();c++) { for(size_t c=0;c<Notebook::get().size();c++) {

64
src/source.cc

@ -84,7 +84,13 @@ std::string Source::FixIt::string(Glib::RefPtr<Gtk::TextBuffer> buffer) {
////////////// //////////////
//// View //// //// View ////
////////////// //////////////
std::unordered_set<Source::View*> Source::View::non_deleted_views;
std::map<boost::filesystem::path, Source::View*> Source::View::views;
Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language): Gsv::View(), SpellCheckView(), DiffView(file_path), language(language), status_diagnostics(0, 0, 0) { Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language): Gsv::View(), SpellCheckView(), DiffView(file_path), language(language), status_diagnostics(0, 0, 0) {
non_deleted_views.emplace(this);
views.emplace(file_path, this);
load(); load();
get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(0)); get_buffer()->place_cursor(get_buffer()->get_iter_at_offset(0));
@ -507,12 +513,41 @@ bool Source::View::load() {
get_buffer()->erase(get_buffer()->begin(), get_buffer()->end()); get_buffer()->erase(get_buffer()->begin(), get_buffer()->end());
bool status=true; bool status=true;
if(language) { if(language) {
if(filesystem::read_non_utf8(file_path, get_buffer())==-1) std::ifstream input(file_path.string(), std::ofstream::binary);
Terminal::get().print("Warning: "+file_path.string()+" is not a valid UTF-8 file. Saving might corrupt the file.\n"); if(input) {
std::stringstream ss;
ss << input.rdbuf();
Glib::ustring ustr=ss.str();
bool valid=true;
Glib::ustring::iterator iter;
while(!ustr.validate(iter)) {
auto next_char_iter=iter;
next_char_iter++;
ustr.replace(iter, next_char_iter, "?");
valid=false;
}
get_buffer()->insert_at_cursor(ustr);
if(!valid)
Terminal::get().print("Warning: "+file_path.string()+" is not a valid UTF-8 file. Saving might corrupt the file.\n");
}
} }
else { else {
if(filesystem::read(file_path, get_buffer())==-1) { std::ifstream input(file_path.string(), std::ofstream::binary);
Terminal::get().print("Error: "+file_path.string()+" is not a valid UTF-8 file.\n", true); if(input) {
std::stringstream ss;
ss << input.rdbuf();
Glib::ustring ustr=ss.str();
bool valid=true;
if(ustr.validate())
get_buffer()->insert_at_cursor(ustr);
else
valid=false;
if(!valid)
Terminal::get().print("Error: "+file_path.string()+" is not a valid UTF-8 file.\n", true);
status=false; status=false;
} }
} }
@ -599,7 +634,7 @@ Gsv::DrawSpacesFlags Source::View::parse_show_whitespace_characters(const std::s
static_cast<Gsv::DrawSpacesFlags>(std::accumulate(out.begin(), out.end(), 0)); static_cast<Gsv::DrawSpacesFlags>(std::accumulate(out.begin(), out.end(), 0));
} }
bool Source::View::save(const std::vector<Source::View*> &views) { bool Source::View::save() {
if(file_path.empty() || !get_buffer()->get_modified()) if(file_path.empty() || !get_buffer()->get_modified())
return false; return false;
if(Config::get().source.cleanup_whitespace_characters) if(Config::get().source.cleanup_whitespace_characters)
@ -612,7 +647,21 @@ bool Source::View::save(const std::vector<Source::View*> &views) {
format_style(false); format_style(false);
} }
if(filesystem::write(file_path, get_buffer())) { std::ofstream output(file_path.string(), std::ofstream::binary);
if(output) {
auto start_iter=get_buffer()->begin();
auto end_iter=start_iter;
bool end_reached=false;
while(!end_reached) {
for(size_t c=0;c<131072;c++) {
if(!end_iter.forward_char()) {
end_reached=true;
break;
}
}
output << get_buffer()->get_text(start_iter, end_iter).c_str();
start_iter=end_iter;
}
boost::system::error_code ec; boost::system::error_code ec;
last_write_time=boost::filesystem::last_write_time(file_path, ec); last_write_time=boost::filesystem::last_write_time(file_path, ec);
if(ec) if(ec)
@ -866,6 +915,9 @@ Source::View::~View() {
delayed_tooltips_connection.disconnect(); delayed_tooltips_connection.disconnect();
renderer_activate_connection.disconnect(); renderer_activate_connection.disconnect();
non_deleted_views.erase(this);
views.erase(file_path);
} }
void Source::View::search_highlight(const std::string &text, bool case_sensitive, bool regex) { void Source::View::search_highlight(const std::string &text, bool case_sensitive, bool regex) {

15
src/source.h

@ -40,13 +40,16 @@ namespace Source {
class View : public SpellCheckView, public DiffView { class View : public SpellCheckView, public DiffView {
public: public:
static std::unordered_set<View*> non_deleted_views;
static std::map<boost::filesystem::path, View*> views;
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();
bool load(); bool load();
void rename(const boost::filesystem::path &path); void rename(const boost::filesystem::path &path);
virtual bool save(const std::vector<Source::View*> &views); virtual bool save();
///Set new text without moving scrolled window ///Set new text without moving scrolled window
void replace_text(const std::string &text); void replace_text(const std::string &text);
@ -67,14 +70,14 @@ namespace Source {
std::function<void()> non_interactive_completion; std::function<void()> non_interactive_completion;
std::function<void(bool)> format_style; std::function<void(bool)> format_style;
std::function<Offset()> get_declaration_location; std::function<Offset()> get_declaration_location;
std::function<std::vector<Offset>(const std::vector<Source::View*> &views)> get_implementation_locations; std::function<std::vector<Offset>()> get_implementation_locations;
std::function<std::vector<Offset>(const std::vector<Source::View*> &views)> get_declaration_or_implementation_locations; std::function<std::vector<Offset>()> get_declaration_or_implementation_locations;
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> >()> get_usages;
std::function<std::string()> get_method; 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;
std::function<std::vector<std::pair<boost::filesystem::path, size_t> >(const std::vector<Source::View*> &views, const std::string &text)> rename_similar_tokens; std::function<void(const std::string &text)> rename_similar_tokens;
std::function<void()> goto_next_diagnostic; std::function<void()> goto_next_diagnostic;
std::function<std::vector<FixIt>()> get_fix_its; std::function<std::vector<FixIt>()> get_fix_its;
std::function<void()> toggle_comments; std::function<void()> toggle_comments;
@ -103,7 +106,7 @@ namespace Source {
bool soft_reparse_needed=false; bool soft_reparse_needed=false;
bool full_reparse_needed=false; bool full_reparse_needed=false;
virtual void soft_reparse() {soft_reparse_needed=false;} virtual void soft_reparse(bool delayed=false) {soft_reparse_needed=false;}
virtual void full_reparse() {full_reparse_needed=false;} virtual void full_reparse() {full_reparse_needed=false;}
protected: protected:
bool parsed=false; bool parsed=false;

384
src/source_clang.cc

@ -11,11 +11,14 @@
#include "selection_dialog.h" #include "selection_dialog.h"
#include "filesystem.h" #include "filesystem.h"
#include "compile_commands.h" #include "compile_commands.h"
#include "usages_clang.h"
clangmm::Index Source::ClangViewParse::clang_index(0, 0); clangmm::Index Source::ClangViewParse::clang_index(0, 0);
Source::ClangViewParse::ClangViewParse(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language): Source::ClangViewParse::ClangViewParse(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language):
Source::View(file_path, language) { Source::View(file_path, language) {
Usages::Clang::erase_cache(file_path);
auto tag_table=get_buffer()->get_tag_table(); auto tag_table=get_buffer()->get_tag_table();
for (auto &item : Config::get().source.clang_types) { for (auto &item : Config::get().source.clang_types) {
if(!tag_table->lookup(item.second)) { if(!tag_table->lookup(item.second)) {
@ -28,17 +31,17 @@ Source::ClangViewParse::ClangViewParse(const boost::filesystem::path &file_path,
parse_initialize(); parse_initialize();
get_buffer()->signal_changed().connect([this]() { get_buffer()->signal_changed().connect([this]() {
soft_reparse(); soft_reparse(true);
}); });
} }
bool Source::ClangViewParse::save(const std::vector<Source::View*> &views) { bool Source::ClangViewParse::save() {
if(!Source::View::save(views)) if(!Source::View::save())
return false; return false;
if(language->get_id()=="chdr" || language->get_id()=="cpphdr") { if(language->get_id()=="chdr" || language->get_id()=="cpphdr") {
for(auto &view: views) { for(auto &view: views) {
if(auto clang_view=dynamic_cast<Source::ClangView*>(view)) { if(auto clang_view=dynamic_cast<Source::ClangView*>(view.second)) {
if(this!=clang_view) if(this!=clang_view)
clang_view->soft_reparse_needed=true; clang_view->soft_reparse_needed=true;
} }
@ -94,7 +97,7 @@ void Source::ClangViewParse::parse_initialize() {
pos++; pos++;
} }
auto &buffer_raw=const_cast<std::string&>(buffer.raw()); auto &buffer_raw=const_cast<std::string&>(buffer.raw());
if(this->language && (this->language->get_id()=="chdr" || this->language->get_id()=="cpphdr")) if(language && (language->get_id()=="chdr" || language->get_id()=="cpphdr"))
clangmm::remove_include_guard(buffer_raw); clangmm::remove_include_guard(buffer_raw);
auto build=Project::Build::create(file_path); auto build=Project::Build::create(file_path);
@ -103,7 +106,11 @@ void Source::ClangViewParse::parse_initialize() {
build->update_default(); build->update_default();
auto arguments=CompileCommands::get_arguments(build->get_default_path(), file_path); auto arguments=CompileCommands::get_arguments(build->get_default_path(), file_path);
clang_tu = std::make_unique<clangmm::TranslationUnit>(clang_index, file_path.string(), arguments, buffer_raw); clang_tu = std::make_unique<clangmm::TranslationUnit>(clang_index, file_path.string(), arguments, buffer_raw);
clang_tokens=clang_tu->get_tokens(0, buffer.bytes()-1); clang_tokens=clang_tu->get_tokens();
clang_tokens_offsets.clear();
clang_tokens_offsets.reserve(clang_tokens->size());
for(auto &token: *clang_tokens)
clang_tokens_offsets.emplace_back(token.get_source_range().get_offsets());
update_syntax(); update_syntax();
status_state="parsing..."; status_state="parsing...";
@ -134,12 +141,16 @@ void Source::ClangViewParse::parse_initialize() {
auto &parse_thread_buffer_raw=const_cast<std::string&>(parse_thread_buffer.raw()); auto &parse_thread_buffer_raw=const_cast<std::string&>(parse_thread_buffer.raw());
if(this->language && (this->language->get_id()=="chdr" || this->language->get_id()=="cpphdr")) if(this->language && (this->language->get_id()=="chdr" || this->language->get_id()=="cpphdr"))
clangmm::remove_include_guard(parse_thread_buffer_raw); clangmm::remove_include_guard(parse_thread_buffer_raw);
auto status=clang_tu->ReparseTranslationUnit(parse_thread_buffer_raw); auto status=clang_tu->reparse(parse_thread_buffer_raw);
parsing_in_progress->done("done"); parsing_in_progress->done("done");
if(status==0) { if(status==0) {
auto expected=ParseProcessState::PROCESSING; auto expected=ParseProcessState::PROCESSING;
if(parse_process_state.compare_exchange_strong(expected, ParseProcessState::POSTPROCESSING)) { if(parse_process_state.compare_exchange_strong(expected, ParseProcessState::POSTPROCESSING)) {
clang_tokens=clang_tu->get_tokens(0, parse_thread_buffer.bytes()-1); clang_tokens=clang_tu->get_tokens();
clang_tokens_offsets.clear();
clang_tokens_offsets.reserve(clang_tokens->size());
for(auto &token: *clang_tokens)
clang_tokens_offsets.emplace_back(token.get_source_range().get_offsets());
clang_diagnostics=clang_tu->get_diagnostics(); clang_diagnostics=clang_tu->get_diagnostics();
parse_lock.unlock(); parse_lock.unlock();
dispatcher.post([this] { dispatcher.post([this] {
@ -180,7 +191,7 @@ void Source::ClangViewParse::parse_initialize() {
}); });
} }
void Source::ClangViewParse::soft_reparse() { void Source::ClangViewParse::soft_reparse(bool delayed) {
soft_reparse_needed=false; soft_reparse_needed=false;
parsed=false; parsed=false;
if(parse_state!=ParseState::PROCESSING) if(parse_state!=ParseState::PROCESSING)
@ -196,7 +207,7 @@ void Source::ClangViewParse::soft_reparse() {
update_status_state(this); update_status_state(this);
} }
return false; return false;
}, 1000); }, delayed?1000:0);
} }
void Source::ClangViewParse::update_syntax() { void Source::ClangViewParse::update_syntax() {
@ -215,23 +226,25 @@ void Source::ClangViewParse::update_syntax() {
buffer->remove_tag_by_name(tag, buffer->begin(), buffer->end()); buffer->remove_tag_by_name(tag, buffer->begin(), buffer->end());
last_syntax_tags.clear(); last_syntax_tags.clear();
for (auto &token : *clang_tokens) { for(size_t c=0;c<clang_tokens->size();++c) {
auto &token=(*clang_tokens)[c];
auto &token_offsets=clang_tokens_offsets[c];
//if(token.get_kind()==clangmm::Token::Kind::Token_Punctuation) //if(token.get_kind()==clangmm::Token::Kind::Token_Punctuation)
//ranges.emplace_back(token.offsets, static_cast<int>(token.get_cursor().get_kind())); //ranges.emplace_back(token_offset, static_cast<int>(token.get_cursor().get_kind()));
auto token_kind=token.get_kind(); auto token_kind=token.get_kind();
if(token_kind==clangmm::Token::Kind::Keyword) if(token_kind==clangmm::Token::Kind::Keyword)
apply_tag(token.offsets, 702); apply_tag(token_offsets, 702);
else if(token_kind==clangmm::Token::Kind::Identifier) { else if(token_kind==clangmm::Token::Kind::Identifier) {
auto cursor_kind=token.get_cursor().get_kind(); auto cursor_kind=token.get_cursor().get_kind();
if(cursor_kind==clangmm::Cursor::Kind::DeclRefExpr || cursor_kind==clangmm::Cursor::Kind::MemberRefExpr) if(cursor_kind==clangmm::Cursor::Kind::DeclRefExpr || cursor_kind==clangmm::Cursor::Kind::MemberRefExpr)
cursor_kind=token.get_cursor().get_referenced().get_kind(); cursor_kind=token.get_cursor().get_referenced().get_kind();
if(cursor_kind!=clangmm::Cursor::Kind::PreprocessingDirective) if(cursor_kind!=clangmm::Cursor::Kind::PreprocessingDirective)
apply_tag(token.offsets, static_cast<int>(cursor_kind)); apply_tag(token_offsets, static_cast<int>(cursor_kind));
} }
else if(token_kind==clangmm::Token::Kind::Literal) else if(token_kind==clangmm::Token::Kind::Literal)
apply_tag(token.offsets, static_cast<int>(clangmm::Cursor::Kind::StringLiteral)); apply_tag(token_offsets, static_cast<int>(clangmm::Cursor::Kind::StringLiteral));
else if(token_kind==clangmm::Token::Kind::Comment) else if(token_kind==clangmm::Token::Kind::Comment)
apply_tag(token.offsets, 705); apply_tag(token_offsets, 705);
} }
} }
@ -350,14 +363,15 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle)
type_tooltips.clear(); type_tooltips.clear();
for(size_t c=clang_tokens->size()-1;c!=static_cast<size_t>(-1);--c) { for(size_t c=clang_tokens->size()-1;c!=static_cast<size_t>(-1);--c) {
auto &token=(*clang_tokens)[c]; auto &token=(*clang_tokens)[c];
auto &token_offsets=clang_tokens_offsets[c];
if(token.is_identifier()) { if(token.is_identifier()) {
if(line==token.offsets.first.line-1 && index>=token.offsets.first.index-1 && index <=token.offsets.second.index-1) { if(line==token_offsets.first.line-1 && index>=token_offsets.first.index-1 && index <=token_offsets.second.index-1) {
auto cursor=token.get_cursor(); auto cursor=token.get_cursor();
auto referenced=cursor.get_referenced(); auto referenced=cursor.get_referenced();
if(referenced) { if(referenced) {
auto start=get_buffer()->get_iter_at_line_index(token.offsets.first.line-1, token.offsets.first.index-1); auto start=get_buffer()->get_iter_at_line_index(token_offsets.first.line-1, token_offsets.first.index-1);
auto end=get_buffer()->get_iter_at_line_index(token.offsets.second.line-1, token.offsets.second.index-1); auto end=get_buffer()->get_iter_at_line_index(token_offsets.second.line-1, token_offsets.second.index-1);
auto create_tooltip_buffer=[this, &token]() { auto create_tooltip_buffer=[this, &token, &token_offsets]() {
auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table()); auto tooltip_buffer=Gtk::TextBuffer::create(get_buffer()->get_tag_table());
tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), "Type: "+token.get_cursor().get_type_description(), "def:note"); tooltip_buffer->insert_with_tag(tooltip_buffer->get_insert()->get_iter(), "Type: "+token.get_cursor().get_type_description(), "def:note");
auto brief_comment=token.get_cursor().get_brief_comments(); auto brief_comment=token.get_cursor().get_brief_comments();
@ -369,8 +383,8 @@ void Source::ClangViewParse::show_type_tooltips(const Gdk::Rectangle &rectangle)
auto location=token.get_cursor().get_referenced().get_source_location(); auto location=token.get_cursor().get_referenced().get_source_location();
Glib::ustring value_type="Value"; Glib::ustring value_type="Value";
auto start=get_buffer()->get_iter_at_line_index(token.offsets.first.line-1, token.offsets.first.index-1); auto start=get_buffer()->get_iter_at_line_index(token_offsets.first.line-1, token_offsets.first.index-1);
auto end=get_buffer()->get_iter_at_line_index(token.offsets.second.line-1, token.offsets.second.index-1); auto end=get_buffer()->get_iter_at_line_index(token_offsets.second.line-1, token_offsets.second.index-1);
auto iter=start; auto iter=start;
while((*iter>='a' && *iter<='z') || (*iter>='A' && *iter<='Z') || (*iter>='0' && *iter<='9') || *iter=='_' || *iter=='.') { while((*iter>='a' && *iter<='z') || (*iter>='A' && *iter<='Z') || (*iter>='0' && *iter<='9') || *iter=='_' || *iter=='.') {
start=iter; start=iter;
@ -436,7 +450,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa
}; };
autocomplete.reparse=[this] { autocomplete.reparse=[this] {
soft_reparse(); soft_reparse(true);
}; };
autocomplete.cancel_reparse=[this] { autocomplete.cancel_reparse=[this] {
@ -651,7 +665,7 @@ const std::unordered_map<std::string, std::string> &Source::ClangViewAutocomplet
Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) : Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language) :
Source::ClangViewParse(file_path, language) { Source::ClangViewParse(file_path, language) {
similar_identifiers_tag=get_buffer()->create_tag(); similar_identifiers_tag=get_buffer()->create_tag();
similar_identifiers_tag->property_weight()=1000; //TODO: Replace 1000 with Pango::WEIGHT_ULTRAHEAVY when debian stable gets updated in 2017 similar_identifiers_tag->property_weight()=Pango::WEIGHT_ULTRAHEAVY;
get_buffer()->signal_changed().connect([this]() { get_buffer()->signal_changed().connect([this]() {
if(last_tagged_identifier) { if(last_tagged_identifier) {
@ -681,82 +695,116 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file
return identifier.spelling; return identifier.spelling;
}; };
rename_similar_tokens=[this](const std::vector<Source::View*> &views, const std::string &text) { rename_similar_tokens=[this](const std::string &text) {
std::vector<std::pair<boost::filesystem::path, size_t> > renamed;
if(!parsed) { if(!parsed) {
Info::get().print("Buffer is parsing"); Info::get().print("Buffer is parsing");
return renamed; return;
} }
auto identifier=get_identifier(); auto identifier=get_identifier();
if(identifier) { if(identifier) {
wait_parsing(views); wait_parsing();
//If rename constructor or destructor, set token to class std::vector<clangmm::TranslationUnit*> translation_units;
if(identifier.kind==clangmm::Cursor::Kind::Constructor || identifier.kind==clangmm::Cursor::Kind::Destructor) { translation_units.emplace_back(clang_tu.get());
auto parent_cursor=identifier.cursor.get_semantic_parent(); for(auto &view: views) {
identifier=Identifier(identifier.spelling, parent_cursor); if(view.second!=this) {
} if(auto clang_view=dynamic_cast<Source::ClangView*>(view.second))
//Special case for class with constructor template translation_units.emplace_back(clang_view->clang_tu.get());
else if(identifier.kind==clangmm::Cursor::Kind::FunctionTemplate) { }
auto parent_cursor=identifier.cursor.get_semantic_parent();
auto kind=parent_cursor.get_kind();
if(identifier.spelling==parent_cursor.get_spelling() &&
(kind==clangmm::Cursor::Kind::ClassDecl || kind==clangmm::Cursor::Kind::ClassTemplate || kind==clangmm::Cursor::Kind::StructDecl))
identifier=Identifier(identifier.spelling, parent_cursor);
} }
auto build=Project::Build::create(this->file_path);
auto usages=Usages::Clang::get_usages(build->project_path, build->get_default_path(), build->get_debug_path(), identifier.spelling, identifier.cursor, translation_units);
std::vector<Source::View*> renamed_views; std::vector<Source::View*> renamed_views;
for(auto &view: views) { std::vector<Usages::Clang::Usages*> usages_renamed;
if(auto clang_view=dynamic_cast<Source::ClangView*>(view)) { for(auto &usage: usages) {
//If rename class, also rename constructors and destructor size_t line_c=usage.lines.size()-1;
std::set<Identifier> identifiers; auto view_it=views.find(usage.path);
identifiers.emplace(identifier); if(view_it!=views.end()) {
auto identifier_cursor_kind=identifier.cursor.get_kind(); view_it->second->get_buffer()->begin_user_action();
if(identifier_cursor_kind==clangmm::Cursor::Kind::ClassDecl || identifier_cursor_kind==clangmm::Cursor::Kind::ClassTemplate || for(auto offset_it=usage.offsets.rbegin();offset_it!=usage.offsets.rend();++offset_it) {
identifier_cursor_kind==clangmm::Cursor::Kind::StructDecl) { auto start_iter=view_it->second->get_buffer()->get_iter_at_line_index(offset_it->first.line-1, offset_it->first.index-1);
for(auto &token: *clang_view->clang_tokens) { auto end_iter=view_it->second->get_buffer()->get_iter_at_line_index(offset_it->second.line-1, offset_it->second.index-1);
auto cursor=token.get_cursor(); view_it->second->get_buffer()->erase(start_iter, end_iter);
auto kind=cursor.get_kind(); start_iter=view_it->second->get_buffer()->get_iter_at_line_index(offset_it->first.line-1, offset_it->first.index-1);
if((kind==clangmm::Cursor::Kind::Constructor || kind==clangmm::Cursor::Kind::Destructor || view_it->second->get_buffer()->insert(start_iter, text);
kind==clangmm::Cursor::Kind::FunctionTemplate) && token.is_identifier()) { if(offset_it->first.index-1<usage.lines[line_c].size())
auto parent_cursor=cursor.get_semantic_parent(); usage.lines[line_c].replace(offset_it->first.index-1, offset_it->second.index-offset_it->first.index, text);
if(parent_cursor.get_kind()==identifier.kind && token.get_spelling()==identifier.spelling && parent_cursor.get_usr_extended()==identifier.usr_extended) { --line_c;
identifiers.emplace(token.get_spelling(), cursor); }
} view_it->second->get_buffer()->end_user_action();
view_it->second->save();
renamed_views.emplace_back(view_it->second);
usages_renamed.emplace_back(&usage);
}
else {
std::string buffer;
{
std::ifstream stream(usage.path.string(), std::ifstream::binary);
if(stream)
buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
}
std::ofstream stream(usage.path.string(), std::ifstream::binary);
if(!buffer.empty() && stream) {
std::vector<size_t> lines_start_pos={0};
for(size_t c=0;c<buffer.size();++c) {
if(buffer[c]=='\n')
lines_start_pos.emplace_back(c+1);
}
for(auto offset_it=usage.offsets.rbegin();offset_it!=usage.offsets.rend();++offset_it) {
auto start_line=offset_it->first.line-1;
auto end_line=offset_it->second.line-1;
if(start_line<lines_start_pos.size() && end_line<lines_start_pos.size()) {
auto start=lines_start_pos[start_line]+offset_it->first.index-1;
auto end=lines_start_pos[end_line]+offset_it->second.index-1;
if(start<buffer.size() && end<=buffer.size())
buffer.replace(start, end-start, text);
} }
if(offset_it->first.index-1<usage.lines[line_c].size())
usage.lines[line_c].replace(offset_it->first.index-1, offset_it->second.index-offset_it->first.index, text);
--line_c;
} }
stream.write(buffer.data(), buffer.size());
usages_renamed.emplace_back(&usage);
} }
else
Terminal::get().print("Error: could not write to file "+usage.path.string()+'\n', true);
}
}
std::vector<std::pair<clangmm::Offset, clangmm::Offset> > offsets; if(!usages_renamed.empty()) {
for(auto &identifier: identifiers) { Terminal::get().print("Renamed ");
auto token_offsets=clang_view->clang_tokens->get_similar_token_offsets(identifier.kind, identifier.spelling, identifier.cursor.get_all_usr_extended()); Terminal::get().print(identifier.spelling, true);
for(auto &token_offset: token_offsets) Terminal::get().print(" to ");
offsets.emplace_back(token_offset); Terminal::get().print(text, true);
} Terminal::get().print(" at:\n");
std::vector<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark> > > marks; }
for(auto &offset: offsets) { for(auto &usage: usages_renamed) {
marks.emplace_back(clang_view->get_buffer()->create_mark(clang_view->get_buffer()->get_iter_at_line_index(offset.first.line-1, offset.first.index-1)), size_t line_c=0;
clang_view->get_buffer()->create_mark(clang_view->get_buffer()->get_iter_at_line_index(offset.second.line-1, offset.second.index-1))); for(auto &offset: usage->offsets) {
Terminal::get().print(filesystem::get_short_path(usage->path).string()+':'+std::to_string(offset.first.line)+':'+std::to_string(offset.first.index)+": ");
auto &line=usage->lines[line_c];
auto index=offset.first.index-1;
unsigned start=0;
for(auto &chr: line) {
if(chr!=' ' && chr!='\t')
break;
++start;
} }
if(!marks.empty()) { if(start<line.size() && index+text.size()<line.size()) {
clang_view->get_buffer()->begin_user_action(); Terminal::get().print(line.substr(start, index-start));
for(auto &mark: marks) { Terminal::get().print(line.substr(index, text.size()), true);
clang_view->get_buffer()->erase(mark.first->get_iter(), mark.second->get_iter()); Terminal::get().print(line.substr(index+text.size()));
clang_view->get_buffer()->insert(mark.first->get_iter(), text);
clang_view->get_buffer()->delete_mark(mark.first);
clang_view->get_buffer()->delete_mark(mark.second);
}
clang_view->get_buffer()->end_user_action();
clang_view->save(views);
renamed_views.emplace_back(clang_view);
renamed.emplace_back(clang_view->file_path, marks.size());
} }
Terminal::get().print("\n");
++line_c;
} }
} }
for(auto &view: renamed_views) for(auto &view: renamed_views)
view->soft_reparse_needed=false; view->soft_reparse_needed=false;
} }
return renamed;
}; };
get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark>& mark){ get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr<Gtk::TextBuffer::Mark>& mark){
@ -850,16 +898,16 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file
return offset; return offset;
}; };
auto implementation_locations=[this](const std::vector<Source::View*> &views) { auto implementation_locations=[this]() {
std::vector<Offset> offsets; std::vector<Offset> offsets;
auto identifier=get_identifier(); auto identifier=get_identifier();
if(identifier) { if(identifier) {
wait_parsing(views); wait_parsing();
//First, look for a definition cursor that is equal //First, look for a definition cursor that is equal
auto identifier_usr=identifier.cursor.get_usr(); auto identifier_usr=identifier.cursor.get_usr();
for(auto &view: views) { for(auto &view: views) {
if(auto clang_view=dynamic_cast<Source::ClangView*>(view)) { if(auto clang_view=dynamic_cast<Source::ClangView*>(view.second)) {
for(auto &token: *clang_view->clang_tokens) { for(auto &token: *clang_view->clang_tokens) {
auto cursor=token.get_cursor(); auto cursor=token.get_cursor();
auto cursor_kind=cursor.get_kind(); auto cursor_kind=cursor.get_kind();
@ -936,12 +984,12 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file
return offsets; return offsets;
}; };
get_implementation_locations=[this, implementation_locations](const std::vector<Source::View*> &views){ get_implementation_locations=[this, implementation_locations](){
if(!parsed) { if(!parsed) {
Info::get().print("Buffer is parsing"); Info::get().print("Buffer is parsing");
return std::vector<Offset>(); return std::vector<Offset>();
} }
auto offsets=implementation_locations(views); auto offsets=implementation_locations();
if(offsets.empty()) if(offsets.empty())
Info::get().print("No implementation found"); Info::get().print("No implementation found");
@ -957,7 +1005,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file
return offsets; return offsets;
}; };
get_declaration_or_implementation_locations=[this, declaration_location, implementation_locations](const std::vector<Source::View*> &views) { get_declaration_or_implementation_locations=[this, declaration_location, implementation_locations]() {
if(!parsed) { if(!parsed) {
Info::get().print("Buffer is parsing"); Info::get().print("Buffer is parsing");
return std::vector<Offset>(); return std::vector<Offset>();
@ -969,9 +1017,11 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file
auto iter=get_buffer()->get_insert()->get_iter(); auto iter=get_buffer()->get_insert()->get_iter();
auto line=static_cast<unsigned>(iter.get_line()); auto line=static_cast<unsigned>(iter.get_line());
auto index=static_cast<unsigned>(iter.get_line_index()); auto index=static_cast<unsigned>(iter.get_line_index());
for(auto &token: *clang_tokens) { for(size_t c=0;c<clang_tokens->size();++c) {
auto &token=(*clang_tokens)[c];
if(token.is_identifier()) { if(token.is_identifier()) {
if(line==token.offsets.first.line-1 && index>=token.offsets.first.index-1 && index<=token.offsets.second.index-1) { auto &token_offsets=clang_tokens_offsets[c];
if(line==token_offsets.first.line-1 && index>=token_offsets.first.index-1 && index<=token_offsets.second.index-1) {
if(clang_isCursorDefinition(token.get_cursor().cx_cursor)>0) if(clang_isCursorDefinition(token.get_cursor().cx_cursor)>0)
is_implementation=true; is_implementation=true;
break; break;
@ -985,7 +1035,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file
offsets.emplace_back(offset); offsets.emplace_back(offset);
} }
else { else {
auto implementation_offsets=implementation_locations(views); auto implementation_offsets=implementation_locations();
if(!implementation_offsets.empty()) { if(!implementation_offsets.empty()) {
offsets=std::move(implementation_offsets); offsets=std::move(implementation_offsets);
} }
@ -1011,7 +1061,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file
return offsets; return offsets;
}; };
get_usages=[this](const std::vector<Source::View*> &views) { get_usages=[this]() {
std::vector<std::pair<Offset, std::string> > usages; std::vector<std::pair<Offset, std::string> > usages;
if(!parsed) { if(!parsed) {
Info::get().print("Buffer is parsing"); Info::get().print("Buffer is parsing");
@ -1019,49 +1069,53 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file
} }
auto identifier=get_identifier(); auto identifier=get_identifier();
if(identifier) { if(identifier) {
wait_parsing(views); wait_parsing();
std::vector<Source::View*> views_reordered;
views_reordered.emplace_back(this); auto embolden_token=[](std::string &line, unsigned token_start_pos, unsigned token_end_pos) {
//markup token as bold
size_t pos=0;
while((pos=line.find('&', pos))!=std::string::npos) {
size_t pos2=line.find(';', pos+2);
if(token_start_pos>pos) {
token_start_pos+=pos2-pos;
token_end_pos+=pos2-pos;
}
else if(token_end_pos>pos)
token_end_pos+=pos2-pos;
else
break;
pos=pos2+1;
}
line.insert(token_end_pos, "</b>");
line.insert(token_start_pos, "<b>");
size_t start_pos=0;
while(start_pos<line.size() && (line[start_pos]==' ' || line[start_pos]=='\t'))
++start_pos;
if(start_pos>0)
line.erase(0, start_pos);
};
std::vector<clangmm::TranslationUnit*> translation_units;
translation_units.emplace_back(clang_tu.get());
for(auto &view: views) { for(auto &view: views) {
if(view!=this) if(view.second!=this) {
views_reordered.emplace_back(view); if(auto clang_view=dynamic_cast<Source::ClangView*>(view.second))
translation_units.emplace_back(clang_view->clang_tu.get());
}
} }
for(auto &view: views_reordered) {
if(auto clang_view=dynamic_cast<Source::ClangView*>(view)) { auto build=Project::Build::create(this->file_path);
auto offsets=clang_view->clang_tokens->get_similar_token_offsets(identifier.kind, identifier.spelling, identifier.cursor.get_all_usr_extended()); auto usages_clang=Usages::Clang::get_usages(build->project_path, build->get_default_path(), build->get_debug_path(), {identifier.spelling}, {identifier.cursor}, translation_units);
for(auto &offset: offsets) { for(auto &usage: usages_clang) {
size_t whitespaces_removed=0; for(size_t c=0;c<usage.offsets.size();++c) {
auto start_iter=clang_view->get_buffer()->get_iter_at_line(offset.first.line-1); std::string line=Glib::Markup::escape_text(usage.lines[c]);
while(!start_iter.ends_line() && (*start_iter==' ' || *start_iter=='\t')) { embolden_token(line, usage.offsets[c].first.index-1, usage.offsets[c].second.index-1);
start_iter.forward_char(); usages.emplace_back(Offset(usage.offsets[c].first.line-1, usage.offsets[c].first.index-1, usage.path), line);
whitespaces_removed++;
}
auto end_iter=clang_view->get_iter_at_line_end(offset.first.line-1);
std::string line=Glib::Markup::escape_text(clang_view->get_buffer()->get_text(start_iter, end_iter));
//markup token as bold
size_t token_start_pos=offset.first.index-1-whitespaces_removed;
size_t token_end_pos=offset.second.index-1-whitespaces_removed;
size_t pos=0;
while((pos=line.find('&', pos))!=std::string::npos) {
size_t pos2=line.find(';', pos+2);
if(token_start_pos>pos) {
token_start_pos+=pos2-pos;
token_end_pos+=pos2-pos;
}
else if(token_end_pos>pos)
token_end_pos+=pos2-pos;
else
break;
pos=pos2+1;
}
line.insert(token_end_pos, "</b>");
line.insert(token_start_pos, "<b>");
usages.emplace_back(Offset(offset.first.line-1, offset.first.index-1, clang_view->file_path), line);
}
} }
} }
} }
if(usages.empty()) if(usages.empty())
Info::get().print("No symbol found at current cursor location"); Info::get().print("No symbol found at current cursor location");
return usages; return usages;
@ -1075,9 +1129,11 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file
auto iter=get_buffer()->get_insert()->get_iter(); auto iter=get_buffer()->get_insert()->get_iter();
auto line=static_cast<unsigned>(iter.get_line()); auto line=static_cast<unsigned>(iter.get_line());
auto index=static_cast<unsigned>(iter.get_line_index()); auto index=static_cast<unsigned>(iter.get_line_index());
for(auto &token: *clang_tokens) { for(size_t c=0;c<clang_tokens->size();++c) {
auto &token=(*clang_tokens)[c];
if(token.is_identifier()) { if(token.is_identifier()) {
if(line==token.offsets.first.line-1 && index>=token.offsets.first.index-1 && index <=token.offsets.second.index-1) { auto &token_offsets=clang_tokens_offsets[c];
if(line==token_offsets.first.line-1 && index>=token_offsets.first.index-1 && index <=token_offsets.second.index-1) {
auto cursor=token.get_cursor(); auto cursor=token.get_cursor();
auto kind=cursor.get_kind(); auto kind=cursor.get_kind();
if(kind==clangmm::Cursor::Kind::FunctionDecl || kind==clangmm::Cursor::Kind::CXXMethod || if(kind==clangmm::Cursor::Kind::FunctionDecl || kind==clangmm::Cursor::Kind::CXXMethod ||
@ -1139,7 +1195,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file
Info::get().print("Buffer is parsing"); Info::get().print("Buffer is parsing");
return methods; return methods;
} }
clangmm::Offset last_offset(-1, -1); clangmm::Offset last_offset{static_cast<unsigned>(-1), static_cast<unsigned>(-1)};
for(auto &token: *clang_tokens) { for(auto &token: *clang_tokens) {
if(token.is_identifier()) { if(token.is_identifier()) {
auto cursor=token.get_cursor(); auto cursor=token.get_cursor();
@ -1215,9 +1271,11 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file
auto iter=get_buffer()->get_insert()->get_iter(); auto iter=get_buffer()->get_insert()->get_iter();
auto line=static_cast<unsigned>(iter.get_line()); auto line=static_cast<unsigned>(iter.get_line());
auto index=static_cast<unsigned>(iter.get_line_index()); auto index=static_cast<unsigned>(iter.get_line_index());
for(auto &token: *clang_tokens) { for(size_t c=0;c<clang_tokens->size();++c) {
auto &token=(*clang_tokens)[c];
if(token.is_identifier()) { if(token.is_identifier()) {
if(line==token.offsets.first.line-1 && index>=token.offsets.first.index-1 && index <=token.offsets.second.index-1) { auto &token_offsets=clang_tokens_offsets[c];
if(line==token_offsets.first.line-1 && index>=token_offsets.first.index-1 && index <=token_offsets.second.index-1) {
auto referenced=token.get_cursor().get_referenced(); auto referenced=token.get_cursor().get_referenced();
if(referenced) { if(referenced) {
auto usr=referenced.get_usr(); auto usr=referenced.get_usr();
@ -1383,7 +1441,8 @@ Source::ClangViewRefactor::Identifier Source::ClangViewRefactor::get_identifier(
for(size_t c=clang_tokens->size()-1;c!=static_cast<size_t>(-1);--c) { for(size_t c=clang_tokens->size()-1;c!=static_cast<size_t>(-1);--c) {
auto &token=(*clang_tokens)[c]; auto &token=(*clang_tokens)[c];
if(token.is_identifier()) { if(token.is_identifier()) {
if(line==token.offsets.first.line-1 && index>=token.offsets.first.index-1 && index <=token.offsets.second.index-1) { auto &token_offsets=clang_tokens_offsets[c];
if(line==token_offsets.first.line-1 && index>=token_offsets.first.index-1 && index <=token_offsets.second.index-1) {
auto referenced=token.get_cursor().get_referenced(); auto referenced=token.get_cursor().get_referenced();
if(referenced) if(referenced)
return Identifier(token.get_spelling(), referenced); return Identifier(token.get_spelling(), referenced);
@ -1393,11 +1452,11 @@ Source::ClangViewRefactor::Identifier Source::ClangViewRefactor::get_identifier(
return Identifier(); return Identifier();
} }
void Source::ClangViewRefactor::wait_parsing(const std::vector<Source::View*> &views) { void Source::ClangViewRefactor::wait_parsing() {
std::unique_ptr<Dialog::Message> message; std::unique_ptr<Dialog::Message> message;
std::vector<Source::ClangView*> clang_views; std::vector<Source::ClangView*> clang_views;
for(auto &view: views) { for(auto &view: views) {
if(auto clang_view=dynamic_cast<Source::ClangView*>(view)) { if(auto clang_view=dynamic_cast<Source::ClangView*>(view.second)) {
if(!clang_view->parsed) { if(!clang_view->parsed) {
clang_views.emplace_back(clang_view); clang_views.emplace_back(clang_view);
if(!message) if(!message)
@ -1506,13 +1565,56 @@ void Source::ClangView::full_reparse() {
} }
void Source::ClangView::async_delete() { void Source::ClangView::async_delete() {
dispatcher.disconnect();
delayed_reparse_connection.disconnect();
delayed_tag_similar_identifiers_connection.disconnect(); delayed_tag_similar_identifiers_connection.disconnect();
parsing_in_progress->cancel("canceled, freeing resources in the background"); parsing_in_progress->cancel("canceled, freeing resources in the background");
parse_state=ParseState::STOP;
delete_thread=std::thread([this](){ views.erase(file_path);
//TODO: Is it possible to stop the clang-process in progress? std::set<boost::filesystem::path> project_paths_in_use;
for(auto &view: views) {
if(dynamic_cast<ClangView*>(view.second)) {
auto build=Project::Build::create(view.first);
if(!build->project_path.empty())
project_paths_in_use.emplace(build->project_path);
}
}
Usages::Clang::erase_unused_caches(project_paths_in_use);
Usages::Clang::cache_in_progress();
if(!get_buffer()->get_modified()) {
if(full_reparse_needed)
full_reparse();
else if(soft_reparse_needed)
soft_reparse();
}
auto before_parse_time=std::time(nullptr);
delete_thread=std::thread([this, before_parse_time, project_paths_in_use=std::move(project_paths_in_use)] {
while(!parsed)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
delayed_reparse_connection.disconnect();
parse_state=ParseState::STOP;
dispatcher.disconnect();
if(get_buffer()->get_modified()) {
std::ifstream stream(file_path.string(), std::ios::binary);
if(stream) {
std::string buffer;
buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
if(language && (language->get_id()=="chdr" || language->get_id()=="cpphdr"))
clangmm::remove_include_guard(buffer);
clang_tu->reparse(buffer);
clang_tokens = clang_tu->get_tokens();
}
else
clang_tokens=nullptr;
}
if(clang_tokens) {
auto build=Project::Build::create(file_path);
Usages::Clang::cache(build->project_path, build->get_default_path(), file_path, before_parse_time, project_paths_in_use, clang_tu.get(), clang_tokens.get());
}
if(full_reparse_thread.joinable()) if(full_reparse_thread.joinable())
full_reparse_thread.join(); full_reparse_thread.join();
if(parse_thread.joinable()) if(parse_thread.joinable())

7
src/source_clang.h

@ -20,15 +20,16 @@ namespace Source {
public: public:
ClangViewParse(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language); ClangViewParse(const boost::filesystem::path &file_path, Glib::RefPtr<Gsv::Language> language);
bool save(const std::vector<Source::View*> &views) override; bool save() override;
void configure() override; void configure() override;
void soft_reparse() override; void soft_reparse(bool delayed=false) override;
protected: protected:
Dispatcher dispatcher; Dispatcher dispatcher;
void parse_initialize(); void parse_initialize();
std::unique_ptr<clangmm::TranslationUnit> clang_tu; std::unique_ptr<clangmm::TranslationUnit> clang_tu;
std::unique_ptr<clangmm::Tokens> clang_tokens; std::unique_ptr<clangmm::Tokens> clang_tokens;
std::vector<std::pair<clangmm::Offset, clangmm::Offset>> clang_tokens_offsets;
sigc::connection delayed_reparse_connection; sigc::connection delayed_reparse_connection;
std::shared_ptr<Terminal::InProgress> parsing_in_progress; std::shared_ptr<Terminal::InProgress> parsing_in_progress;
@ -94,7 +95,7 @@ namespace Source {
sigc::connection delayed_tag_similar_identifiers_connection; sigc::connection delayed_tag_similar_identifiers_connection;
private: private:
Identifier get_identifier(); Identifier get_identifier();
void wait_parsing(const std::vector<Source::View*> &views); void wait_parsing();
std::list<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark> > > similar_identifiers_marks; std::list<std::pair<Glib::RefPtr<Gtk::TextMark>, Glib::RefPtr<Gtk::TextMark> > > similar_identifiers_marks;
void tag_similar_identifiers(const Identifier &identifier); void tag_similar_identifiers(const Identifier &identifier);

19
src/terminal.cc

@ -3,6 +3,7 @@
#include "project.h" #include "project.h"
#include "info.h" #include "info.h"
#include "notebook.h" #include "notebook.h"
#include "filesystem.h"
#include <iostream> #include <iostream>
Terminal::InProgress::InProgress(const std::string& start_msg): stop(false) { Terminal::InProgress::InProgress(const std::string& start_msg): stop(false) {
@ -45,7 +46,7 @@ void Terminal::InProgress::cancel(const std::string& msg) {
Terminal::Terminal() { Terminal::Terminal() {
bold_tag=get_buffer()->create_tag(); bold_tag=get_buffer()->create_tag();
bold_tag->property_weight()=PANGO_WEIGHT_BOLD; bold_tag->property_weight()=Pango::WEIGHT_ULTRAHEAVY;
link_tag=get_buffer()->create_tag(); link_tag=get_buffer()->create_tag();
link_tag->property_underline()=Pango::Underline::UNDERLINE_SINGLE; link_tag->property_underline()=Pango::Underline::UNDERLINE_SINGLE;
@ -176,7 +177,7 @@ bool Terminal::on_motion_notify_event(GdkEventMotion *motion_event) {
} }
std::tuple<size_t, size_t, std::string, std::string, std::string> Terminal::find_link(const std::string &line) { std::tuple<size_t, size_t, std::string, std::string, std::string> Terminal::find_link(const std::string &line) {
const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" //compile warning/error const static std::regex link_regex("^([A-Z]:)?([^:]+):([0-9]+):([0-9]+): .*$|" //compile warning/error/rename usages
"^Assertion failed: .*file ([A-Z]:)?([^:]+), line ([0-9]+)\\.$|" //clang assert() "^Assertion failed: .*file ([A-Z]:)?([^:]+), line ([0-9]+)\\.$|" //clang assert()
"^[^:]*: ([A-Z]:)?([^:]+):([0-9]+): .* Assertion .* failed\\.$|" //gcc assert() "^[^:]*: ([A-Z]:)?([^:]+):([0-9]+): .* Assertion .* failed\\.$|" //gcc assert()
"^ERROR:([A-Z]:)?([^:]+):([0-9]+):.*$"); //g_assert (glib.h) "^ERROR:([A-Z]:)?([^:]+):([0-9]+):.*$"); //g_assert (glib.h)
@ -280,7 +281,7 @@ size_t Terminal::print(const std::string &message, bool bold){
umessage.replace(iter, next_char_iter, "?"); umessage.replace(iter, next_char_iter, "?");
} }
auto start_mark=get_buffer()->create_mark(get_buffer()->get_iter_at_line(get_buffer()->get_insert()->get_iter().get_line())); auto start_mark=get_buffer()->create_mark(get_buffer()->get_iter_at_line(get_buffer()->end().get_line()));
if(bold) if(bold)
get_buffer()->insert_with_tag(get_buffer()->end(), umessage, bold_tag); get_buffer()->insert_with_tag(get_buffer()->end(), umessage, bold_tag);
else else
@ -387,6 +388,18 @@ bool Terminal::on_button_press_event(GdkEventButton* button_event) {
std::string line=std::get<3>(link); std::string line=std::get<3>(link);
std::string index=std::get<4>(link); std::string index=std::get<4>(link);
if(!path.empty() && *path.begin()=="~") { // boost::filesystem does not recognize ~
boost::filesystem::path corrected_path;
corrected_path=filesystem::get_home_path();
if(!corrected_path.empty()) {
auto it=path.begin();
++it;
for(;it!=path.end();++it)
corrected_path/=*it;
path=corrected_path;
}
}
if(path.is_relative()) { if(path.is_relative()) {
if(Project::current) { if(Project::current) {
auto absolute_path=Project::current->build->get_default_path()/path; auto absolute_path=Project::current->build->get_default_path()/path;

714
src/usages_clang.cc

@ -0,0 +1,714 @@
#include "usages_clang.h"
#include "compile_commands.h"
#include "config.h"
#include "dialogs.h"
#include "filesystem.h"
#include <chrono>
#include <fstream>
#include <regex>
#include <thread>
// #include <iostream> //TODO: remove
const boost::filesystem::path Usages::Clang::cache_folder = ".usages_clang";
std::map<boost::filesystem::path, Usages::Clang::Cache> Usages::Clang::caches;
std::mutex Usages::Clang::caches_mutex;
std::atomic<size_t> Usages::Clang::cache_in_progress_count(0);
bool Usages::Clang::Cache::Cursor::operator==(const Cursor &o) {
for(auto &usr : usrs) {
if(o.usrs.count(usr))
return true;
}
return false;
}
Usages::Clang::Cache::Cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path,
std::time_t before_parse_time, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *clang_tokens)
: project_path(project_path), build_path(build_path) {
for(auto &clang_token : *clang_tokens) {
tokens.emplace_back(Token{clang_token.get_spelling(), clang_token.get_source_range().get_offsets(), static_cast<size_t>(-1)});
if(clang_token.is_identifier()) {
auto clang_cursor = clang_token.get_cursor().get_referenced();
if(clang_cursor) {
Cursor cursor{clang_cursor.get_kind(), clang_cursor.get_all_usr_extended()};
for(size_t c = 0; c < cursors.size(); ++c) {
if(cursor == cursors[c]) {
tokens.back().cursor_id = c;
break;
}
}
if(tokens.back().cursor_id == static_cast<size_t>(-1)) {
cursors.emplace_back(cursor);
tokens.back().cursor_id = cursors.size() - 1;
}
}
}
}
boost::system::error_code ec;
auto last_write_time = boost::filesystem::last_write_time(path, ec);
if(ec)
last_write_time = 0;
if(last_write_time > before_parse_time)
last_write_time = 0;
paths_and_last_write_times.emplace(path, last_write_time);
class VisitorData {
public:
const boost::filesystem::path &project_path;
const boost::filesystem::path &path;
std::time_t before_parse_time;
std::map<boost::filesystem::path, std::time_t> &paths_and_last_write_times;
};
VisitorData visitor_data{project_path, path, before_parse_time, paths_and_last_write_times};
clang_getInclusions(translation_unit->cx_tu, [](CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData data) {
auto visitor_data = static_cast<VisitorData *>(data);
auto path = filesystem::get_normal_path(clangmm::to_string(clang_getFileName(included_file)));
if(filesystem::file_in_path(path, visitor_data->project_path)) {
for(unsigned c = 0; c < include_len; ++c) {
auto from_path = filesystem::get_normal_path(clangmm::SourceLocation(inclusion_stack[c]).get_path());
if(from_path == visitor_data->path) {
boost::system::error_code ec;
auto last_write_time = boost::filesystem::last_write_time(path, ec);
if(ec)
last_write_time = 0;
if(last_write_time > visitor_data->before_parse_time)
last_write_time = 0;
visitor_data->paths_and_last_write_times.emplace(path, last_write_time);
break;
}
}
}
},
&visitor_data);
}
std::vector<std::pair<clangmm::Offset, clangmm::Offset>> Usages::Clang::Cache::get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling,
const std::unordered_set<std::string> &usrs) const {
std::vector<std::pair<clangmm::Offset, clangmm::Offset>> offsets;
for(auto &token : tokens) {
if(token.cursor_id != static_cast<size_t>(-1)) {
auto &cursor = cursors[token.cursor_id];
if(clangmm::Cursor::is_similar_kind(cursor.kind, kind) && token.spelling == spelling) {
for(auto &usr : cursor.usrs) {
if(usrs.count(usr)) {
offsets.emplace_back(token.offsets);
break;
}
}
}
}
}
return offsets;
}
std::vector<Usages::Clang::Usages> Usages::Clang::get_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path,
const std::string &spelling, const clangmm::Cursor &cursor, const std::vector<clangmm::TranslationUnit *> &translation_units) {
std::vector<Usages> usages;
if(spelling.empty())
return usages;
PathSet visited;
auto usr_extended = cursor.get_usr_extended();
if(!usr_extended.empty() && usr_extended[0] >= '0' && usr_extended[0] <= '9') { //if declared within a function, return
if(!translation_units.empty())
add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, translation_units.front(), false);
return usages;
}
for(auto &translation_unit : translation_units)
add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, translation_unit, false);
for(auto &translation_unit : translation_units)
add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, translation_unit, false);
if(project_path.empty())
return usages;
auto paths = find_paths(project_path, build_path, debug_path);
auto pair = parse_paths(spelling, paths);
auto pair2 = find_potential_paths(cursor.get_canonical().get_source_location().get_path(), project_path, pair.first, pair.second);
auto &potential_paths = pair2.first;
auto &all_includes = pair2.second;
// Remove visited paths
for(auto it = potential_paths.begin(); it != potential_paths.end();) {
if(visited.find(*it) != visited.end())
it = potential_paths.erase(it);
else
++it;
}
// Wait for current caching to finish
std::unique_ptr<Dialog::Message> message;
const std::string message_string = "Please wait while finding usages";
if(cache_in_progress_count != 0) {
message = std::make_unique<Dialog::Message>(message_string);
while(cache_in_progress_count != 0) {
while(Gtk::Main::events_pending())
Gtk::Main::iteration(false);
}
}
// Use cache
for(auto it = potential_paths.begin(); it != potential_paths.end();) {
std::unique_lock<std::mutex> lock(caches_mutex);
auto caches_it = caches.find(*it);
// Load cache from file if not found in memory and if cache file exists
if(caches_it == caches.end()) {
auto cache = read_cache(project_path, build_path, *it);
if(cache) {
auto pair = caches.emplace(*it, std::move(cache));
caches_it = pair.first;
}
}
if(caches_it != caches.end()) {
if(add_usages_from_cache(caches_it->first, usages, visited, spelling, cursor, caches_it->second))
it = potential_paths.erase(it);
else {
caches.erase(caches_it);
++it;
}
}
else
++it;
}
// Remove paths that has been included
for(auto it = potential_paths.begin(); it != potential_paths.end();) {
if(all_includes.find(*it) != all_includes.end())
it = potential_paths.erase(it);
else
++it;
}
// Parse potential paths
if(!potential_paths.empty()) {
if(!message)
message = std::make_unique<Dialog::Message>(message_string);
std::vector<std::thread> threads;
auto it = potential_paths.begin();
auto number_of_threads = Config::get().source.clang_usages_threads;
if(number_of_threads == static_cast<unsigned>(-1)) {
number_of_threads = std::thread::hardware_concurrency();
if(number_of_threads == 0)
number_of_threads = 1;
}
for(unsigned thread_id = 0; thread_id < number_of_threads; ++thread_id) {
threads.emplace_back([&potential_paths, &it, &build_path,
&project_path, &usages, &visited, &spelling, &cursor] {
while(true) {
boost::filesystem::path path;
{
static std::mutex mutex;
std::unique_lock<std::mutex> lock(mutex);
if(it == potential_paths.end())
return;
path = *it;
++it;
}
clangmm::Index index(0, 0);
{
static std::mutex mutex;
std::unique_lock<std::mutex> lock(mutex);
// std::cout << "parsing: " << path << std::endl;
}
// auto before_time = std::chrono::system_clock::now();
std::ifstream stream(path.string(), std::ifstream::binary);
std::string buffer;
buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
auto arguments = CompileCommands::get_arguments(build_path, path);
arguments.emplace_back("-w"); // Disable all warnings
for(auto it = arguments.begin(); it != arguments.end();) { // remove comments from system headers
if(*it == "-fretain-comments-from-system-headers")
it = arguments.erase(it);
else
++it;
}
int flags = CXTranslationUnit_Incomplete;
#if CINDEX_VERSION_MAJOR > 0 || (CINDEX_VERSION_MAJOR == 0 && CINDEX_VERSION_MINOR >= 35)
flags |= CXTranslationUnit_KeepGoing;
#endif
clangmm::TranslationUnit translation_unit(index, path.string(), arguments, buffer, flags);
{
static std::mutex mutex;
std::unique_lock<std::mutex> lock(mutex);
add_usages(project_path, build_path, path, usages, visited, spelling, cursor, &translation_unit, true);
add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, &translation_unit, true);
}
// auto time = std::chrono::system_clock::now();
// std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(time - before_time).count() << std::endl;
}
});
}
for(auto &thread : threads)
thread.join();
}
if(message)
message->hide();
return usages;
}
void Usages::Clang::cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path,
std::time_t before_parse_time, const PathSet &project_paths_in_use, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *tokens) {
class ScopeExit {
public:
std::function<void()> f;
~ScopeExit() {
f();
}
};
ScopeExit scope_exit{[] {
--cache_in_progress_count;
}};
if(project_path.empty())
return;
{
std::unique_lock<std::mutex> lock(caches_mutex);
if(project_paths_in_use.count(project_path)) {
caches.erase(path);
caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens));
}
else
write_cache(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens));
}
class VisitorData {
public:
const boost::filesystem::path &project_path;
PathSet paths;
};
VisitorData visitor_data{project_path, {}};
auto translation_unit_cursor = clang_getTranslationUnitCursor(translation_unit->cx_tu);
clang_visitChildren(translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) {
auto visitor_data = static_cast<VisitorData *>(data);
auto path = filesystem::get_normal_path(clangmm::Cursor(cx_cursor).get_source_location().get_path());
if(filesystem::file_in_path(path, visitor_data->project_path))
visitor_data->paths.emplace(path);
return CXChildVisit_Continue;
},
&visitor_data);
visitor_data.paths.erase(path);
for(auto &path : visitor_data.paths) {
boost::system::error_code ec;
auto file_size = boost::filesystem::file_size(path, ec);
if(file_size == static_cast<boost::uintmax_t>(-1) || ec)
continue;
auto tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1);
std::unique_lock<std::mutex> lock(caches_mutex);
if(project_paths_in_use.count(project_path)) {
caches.erase(path);
caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get()));
}
else
write_cache(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get()));
}
}
void Usages::Clang::erase_unused_caches(const PathSet &project_paths_in_use) {
std::unique_lock<std::mutex> lock(caches_mutex);
for(auto it = caches.begin(); it != caches.end();) {
bool found = false;
for(auto &project_path : project_paths_in_use) {
if(filesystem::file_in_path(it->first, project_path)) {
found = true;
break;
}
}
if(!found) {
write_cache(it->first, it->second);
it = caches.erase(it);
}
else
++it;
}
}
void Usages::Clang::erase_cache(const boost::filesystem::path &path) {
std::unique_lock<std::mutex> lock(caches_mutex);
auto it = caches.find(path);
if(it == caches.end())
return;
auto paths_and_last_write_times = std::move(it->second.paths_and_last_write_times);
for(auto &path_and_last_write_time : paths_and_last_write_times)
caches.erase(path_and_last_write_time.first);
}
void Usages::Clang::erase_all_caches_for_project(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path) {
if(project_path.empty())
return;
if(cache_in_progress_count != 0)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::unique_lock<std::mutex> lock(caches_mutex);
boost::system::error_code ec;
auto usages_clang_path = build_path / cache_folder;
if(boost::filesystem::exists(usages_clang_path, ec) && boost::filesystem::is_directory(usages_clang_path, ec)) {
for(boost::filesystem::directory_iterator it(usages_clang_path), end; it != end; ++it) {
if(it->path().extension() == ".usages")
boost::filesystem::remove(it->path(), ec);
}
}
for(auto it = caches.begin(); it != caches.end();) {
if(filesystem::file_in_path(it->first, project_path))
it = caches.erase(it);
else
++it;
}
}
void Usages::Clang::cache_in_progress() {
++cache_in_progress_count;
}
void Usages::Clang::add_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path_,
std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, clangmm::Cursor cursor,
clangmm::TranslationUnit *translation_unit, bool store_in_cache) {
std::unique_ptr<clangmm::Tokens> tokens;
boost::filesystem::path path;
auto before_parse_time = std::time(nullptr);
auto all_usr_extended = cursor.get_all_usr_extended();
if(path_.empty()) {
path = clangmm::to_string(clang_getTranslationUnitSpelling(translation_unit->cx_tu));
if(visited.find(path) != visited.end() || !filesystem::file_in_path(path, project_path))
return;
tokens = translation_unit->get_tokens();
}
else {
path = path_;
if(visited.find(path) != visited.end() || !filesystem::file_in_path(path, project_path))
return;
boost::system::error_code ec;
auto file_size = boost::filesystem::file_size(path, ec);
if(file_size == static_cast<boost::uintmax_t>(-1) || ec)
return;
tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1);
}
auto offsets = tokens->get_similar_token_offsets(cursor.get_kind(), spelling, all_usr_extended);
std::vector<std::string> lines;
for(auto &offset : offsets) {
std::string line;
auto line_nr = offset.second.line;
for(auto &token : *tokens) {
auto offset = token.get_source_location().get_offset();
if(offset.line == line_nr) {
while(line.size() < offset.index - 1)
line += ' ';
line += token.get_spelling();
}
}
lines.emplace_back(std::move(line));
}
if(store_in_cache && filesystem::file_in_path(path, project_path)) {
std::unique_lock<std::mutex> lock(caches_mutex);
caches.erase(path);
caches.emplace(path, Cache(project_path, build_path, path, before_parse_time, translation_unit, tokens.get()));
}
visited.emplace(path);
if(!offsets.empty())
usages.emplace_back(Usages{std::move(path), std::move(offsets), lines});
}
bool Usages::Clang::add_usages_from_cache(const boost::filesystem::path &path, std::vector<Usages> &usages, PathSet &visited,
const std::string &spelling, const clangmm::Cursor &cursor, const Cache &cache) {
for(auto &path_and_last_write_time : cache.paths_and_last_write_times) {
boost::system::error_code ec;
auto last_write_time = boost::filesystem::last_write_time(path_and_last_write_time.first, ec);
if(ec || last_write_time > path_and_last_write_time.second) {
// std::cout << "updated file: " << path_and_last_write_time.first << ", included from " << path << std::endl;
return false;
}
}
auto offsets = cache.get_similar_token_offsets(cursor.get_kind(), spelling, cursor.get_all_usr_extended());
std::vector<std::string> lines;
for(auto &offset : offsets) {
std::string line;
auto line_nr = offset.second.line;
for(auto &token : cache.tokens) {
auto &offset = token.offsets.first;
if(offset.line == line_nr) {
while(line.size() < offset.index - 1)
line += ' ';
line += token.spelling;
}
}
lines.emplace_back(std::move(line));
}
visited.emplace(path);
if(!offsets.empty())
usages.emplace_back(Usages{path, std::move(offsets), lines});
return true;
}
void Usages::Clang::add_usages_from_includes(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path,
std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, const clangmm::Cursor &cursor,
clangmm::TranslationUnit *translation_unit, bool store_in_cache) {
if(project_path.empty())
return;
class VisitorData {
public:
const boost::filesystem::path &project_path;
const std::string &spelling;
PathSet &visited;
PathSet paths;
};
VisitorData visitor_data{project_path, spelling, visited, {}};
auto translation_unit_cursor = clang_getTranslationUnitCursor(translation_unit->cx_tu);
clang_visitChildren(translation_unit_cursor, [](CXCursor cx_cursor, CXCursor cx_parent, CXClientData data) {
auto visitor_data = static_cast<VisitorData *>(data);
auto path = filesystem::get_normal_path(clangmm::Cursor(cx_cursor).get_source_location().get_path());
if(visitor_data->visited.find(path) == visitor_data->visited.end() && filesystem::file_in_path(path, visitor_data->project_path))
visitor_data->paths.emplace(path);
return CXChildVisit_Continue;
},
&visitor_data);
for(auto &path : visitor_data.paths)
add_usages(project_path, build_path, path, usages, visited, spelling, cursor, translation_unit, store_in_cache);
}
Usages::Clang::PathSet Usages::Clang::find_paths(const boost::filesystem::path &project_path,
const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path) {
PathSet paths;
CompileCommands compile_commands(build_path);
for(boost::filesystem::recursive_directory_iterator it(project_path), end; it != end; ++it) {
auto &path = it->path();
if(!boost::filesystem::is_regular_file(path)) {
if(path == build_path || path == debug_path || path.filename() == ".git")
it.no_push();
continue;
}
if(is_header(path))
paths.emplace(path);
else if(is_source(path)) {
for(auto &command : compile_commands.commands) {
if(filesystem::get_normal_path(command.file) == path) {
paths.emplace(path);
break;
}
}
}
}
return paths;
}
bool Usages::Clang::is_header(const boost::filesystem::path &path) {
auto ext = path.extension();
if(ext == ".h" || // c headers
ext == ".hh" || ext == ".hp" || ext == ".hpp" || ext == ".h++" || ext == ".tcc") // c++ headers
return true;
else
return false;
}
bool Usages::Clang::is_source(const boost::filesystem::path &path) {
auto ext = path.extension();
if(ext == ".c" || // c sources
ext == ".cpp" || ext == ".cxx" || ext == ".cc" || ext == ".C" || ext == ".c++") // c++ sources
return true;
else
return false;
}
std::pair<std::map<boost::filesystem::path, Usages::Clang::PathSet>, Usages::Clang::PathSet> Usages::Clang::parse_paths(const std::string &spelling, const PathSet &paths) {
std::map<boost::filesystem::path, PathSet> paths_includes;
PathSet paths_with_spelling;
const static std::regex include_regex("^[ \t]*#[ \t]*include[ \t]*[\"]([^\"]+)[\"].*$");
auto is_spelling_char = [](char chr) {
return (chr >= 'a' && chr <= 'z') || (chr >= 'A' && chr <= 'Z') || (chr >= '0' && chr <= '9') || chr == '_';
};
for(auto &path : paths) {
auto paths_includes_it = paths_includes.emplace(path, PathSet()).first;
bool paths_with_spelling_emplaced = false;
std::ifstream stream(path.string(), std::ifstream::binary);
if(!stream)
continue;
std::string line;
while(std::getline(stream, line)) {
std::smatch sm;
if(std::regex_match(line, sm, include_regex)) {
boost::filesystem::path path(sm[1].str());
boost::filesystem::path include_path;
// remove .. and .
for(auto &part : path) {
if(part == "..")
include_path = include_path.parent_path();
else if(part == ".")
continue;
else
include_path /= part;
}
auto distance = std::distance(include_path.begin(), include_path.end());
for(auto &path : paths) {
auto path_distance = std::distance(path.begin(), path.end());
if(path_distance >= distance) {
auto it = path.begin();
std::advance(it, path_distance - distance);
if(std::equal(it, path.end(), include_path.begin(), include_path.end()))
paths_includes_it->second.emplace(path);
}
}
}
else if(!paths_with_spelling_emplaced) {
auto pos = line.find(spelling);
if(pos != std::string::npos &&
((!spelling.empty() && !is_spelling_char(spelling[0])) ||
((pos == 0 || !is_spelling_char(line[pos - 1])) &&
(pos + spelling.size() >= line.size() - 1 || !is_spelling_char(line[pos + spelling.size()]))))) {
paths_with_spelling.emplace(path);
paths_with_spelling_emplaced = true;
}
}
}
}
return {paths_includes, paths_with_spelling};
}
Usages::Clang::PathSet Usages::Clang::get_all_includes(const boost::filesystem::path &path, const std::map<boost::filesystem::path, PathSet> &paths_includes) {
PathSet all_includes;
class Recursive {
public:
static void f(PathSet &all_includes, const boost::filesystem::path &path,
const std::map<boost::filesystem::path, PathSet> &paths_includes) {
auto paths_includes_it = paths_includes.find(path);
if(paths_includes_it != paths_includes.end()) {
for(auto &include : paths_includes_it->second) {
auto pair = all_includes.emplace(include);
if(pair.second)
f(all_includes, include, paths_includes);
}
}
}
};
Recursive::f(all_includes, path, paths_includes);
return all_includes;
}
std::pair<Usages::Clang::PathSet, Usages::Clang::PathSet> Usages::Clang::find_potential_paths(const boost::filesystem::path &path, const boost::filesystem::path &project_path,
const std::map<boost::filesystem::path, PathSet> &paths_includes, const PathSet &paths_with_spelling) {
PathSet potential_paths;
PathSet all_includes;
if(filesystem::file_in_path(path, project_path)) {
for(auto &path_with_spelling : paths_with_spelling) {
auto path_all_includes = get_all_includes(path_with_spelling, paths_includes);
if((path_all_includes.find(path) != path_all_includes.end() || path_with_spelling == path)) {
potential_paths.emplace(path_with_spelling);
for(auto &include : path_all_includes)
all_includes.emplace(include);
}
}
}
else {
for(auto &path_with_spelling : paths_with_spelling) {
potential_paths.emplace(path_with_spelling);
auto path_all_includes = get_all_includes(path_with_spelling, paths_includes);
for(auto &include : path_all_includes)
all_includes.emplace(include);
}
}
return {potential_paths, all_includes};
}
void Usages::Clang::write_cache(const boost::filesystem::path &path, const Clang::Cache &cache) {
auto cache_path = cache.build_path / cache_folder;
boost::system::error_code ec;
if(!boost::filesystem::exists(cache_path, ec)) {
boost::filesystem::create_directory(cache_path, ec);
if(ec)
return;
}
else if(!boost::filesystem::is_directory(cache_path, ec) || ec)
return;
auto path_str = filesystem::get_relative_path(path, cache.project_path).string();
for(auto &chr : path_str) {
if(chr == '/' || chr == '\\')
chr = '_';
}
auto full_cache_path = cache_path / (path_str + ".usages");
std::ofstream stream(full_cache_path.string());
if(stream) {
try {
boost::archive::text_oarchive text_oarchive(stream);
text_oarchive << cache;
}
catch(...) {
}
}
}
Usages::Clang::Cache Usages::Clang::read_cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path) {
auto path_str = filesystem::get_relative_path(path, project_path).string();
for(auto &chr : path_str) {
if(chr == '/' || chr == '\\')
chr = '_';
}
auto cache_path = build_path / cache_folder / (path_str + ".usages");
boost::system::error_code ec;
if(boost::filesystem::exists(cache_path, ec)) {
std::ifstream stream(cache_path.string());
if(stream) {
Cache cache;
boost::archive::text_iarchive text_iarchive(stream);
try {
text_iarchive >> cache;
return cache;
}
catch(...) {
}
}
}
return Cache();
}

151
src/usages_clang.h

@ -0,0 +1,151 @@
#ifndef JUCI_USAGES_CLANG_H_
#define JUCI_USAGES_CLANG_H_
#include "clangmm.h"
#include <atomic>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/filesystem.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/unordered_set.hpp>
#include <boost/serialization/vector.hpp>
#include <map>
#include <mutex>
#include <regex>
#include <set>
#include <unordered_set>
namespace boost {
namespace serialization {
template <class Archive>
void serialize(Archive &ar, boost::filesystem::path &path, const unsigned int version) {
std::string path_str;
if(Archive::is_saving::value)
path_str = path.string();
ar &path_str;
if(Archive::is_loading::value)
path = path_str;
}
} // namespace serialization
} // namespace boost
namespace Usages {
class Clang {
public:
typedef std::set<boost::filesystem::path> PathSet;
class Usages {
public:
boost::filesystem::path path;
std::vector<std::pair<clangmm::Offset, clangmm::Offset>> offsets;
std::vector<std::string> lines;
};
class Cache {
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive &ar, const unsigned int version) {
ar &project_path;
ar &build_path;
ar &tokens;
ar &cursors;
ar &paths_and_last_write_times;
}
public:
class Cursor {
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive &ar, const unsigned int version) {
ar &kind;
ar &usrs;
}
public:
clangmm::Cursor::Kind kind;
std::unordered_set<std::string> usrs;
bool operator==(const Cursor &o);
};
class Token {
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive &ar, const unsigned int version) {
ar &spelling;
ar &offsets.first.line &offsets.first.index;
ar &offsets.second.line &offsets.second.index;
ar &cursor_id;
}
public:
std::string spelling;
std::pair<clangmm::Offset, clangmm::Offset> offsets;
size_t cursor_id;
};
boost::filesystem::path project_path;
boost::filesystem::path build_path;
std::vector<Token> tokens;
std::vector<Cursor> cursors;
std::map<boost::filesystem::path, std::time_t> paths_and_last_write_times;
Cache() {}
Cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path,
std::time_t before_parse_time, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *clang_tokens);
operator bool() const { return !paths_and_last_write_times.empty(); }
std::vector<std::pair<clangmm::Offset, clangmm::Offset>> get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling,
const std::unordered_set<std::string> &usrs) const;
};
private:
const static boost::filesystem::path cache_folder;
static std::map<boost::filesystem::path, Cache> caches;
static std::mutex caches_mutex;
static std::atomic<size_t> cache_in_progress_count;
public:
static std::vector<Usages> get_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path,
const std::string &spelling, const clangmm::Cursor &cursor, const std::vector<clangmm::TranslationUnit *> &translation_units);
static void cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path,
std::time_t before_parse_time, const PathSet &project_paths_in_use, clangmm::TranslationUnit *translation_unit, clangmm::Tokens *tokens);
static void erase_unused_caches(const PathSet &project_paths_in_use);
static void erase_cache(const boost::filesystem::path &path);
static void erase_all_caches_for_project(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path);
static void cache_in_progress();
private:
static void add_usages(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path_,
std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, clangmm::Cursor cursor,
clangmm::TranslationUnit *translation_unit, bool store_in_cache);
static bool add_usages_from_cache(const boost::filesystem::path &path, std::vector<Usages> &usages, PathSet &visited,
const std::string &spelling, const clangmm::Cursor &cursor, const Cache &cache);
static void add_usages_from_includes(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path,
std::vector<Usages> &usages, PathSet &visited, const std::string &spelling, const clangmm::Cursor &cursor,
clangmm::TranslationUnit *translation_unit, bool store_in_cache);
static PathSet find_paths(const boost::filesystem::path &project_path,
const boost::filesystem::path &build_path, const boost::filesystem::path &debug_path);
static bool is_header(const boost::filesystem::path &path);
static bool is_source(const boost::filesystem::path &path);
static std::pair<std::map<boost::filesystem::path, PathSet>, PathSet> parse_paths(const std::string &spelling, const PathSet &paths);
static PathSet get_all_includes(const boost::filesystem::path &path, const std::map<boost::filesystem::path, PathSet> &paths_includes);
static std::pair<Clang::PathSet, Clang::PathSet> find_potential_paths(const boost::filesystem::path &project_path, const boost::filesystem::path &include_path,
const std::map<boost::filesystem::path, PathSet> &paths_includes, const PathSet &paths_with_spelling);
static void write_cache(const boost::filesystem::path &path, const Cache &cache);
static Cache read_cache(const boost::filesystem::path &project_path, const boost::filesystem::path &build_path, const boost::filesystem::path &path);
};
} // namespace Usages
#endif // JUCI_USAGES_CLANG_H_

25
src/window.cc

@ -217,6 +217,10 @@ void Window::set_menu_actions() {
}); });
menu.add_action("quit", [this]() { menu.add_action("quit", [this]() {
close(); close();
while(!Source::View::non_deleted_views.empty()) {
while(Gtk::Main::events_pending())
Gtk::Main::iteration(false);
}
}); });
menu.add_action("new_file", [this]() { menu.add_action("new_file", [this]() {
@ -869,20 +873,20 @@ void Window::set_menu_actions() {
menu.add_action("source_goto_implementation", [this, goto_selected_location]() { menu.add_action("source_goto_implementation", [this, goto_selected_location]() {
if(auto view=Notebook::get().get_current_view()) { if(auto view=Notebook::get().get_current_view()) {
if(view->get_implementation_locations) if(view->get_implementation_locations)
goto_selected_location(view, view->get_implementation_locations(Notebook::get().get_views())); goto_selected_location(view, view->get_implementation_locations());
} }
}); });
menu.add_action("source_goto_declaration_or_implementation", [this, goto_selected_location]() { menu.add_action("source_goto_declaration_or_implementation", [this, goto_selected_location]() {
if(auto view=Notebook::get().get_current_view()) { if(auto view=Notebook::get().get_current_view()) {
if(view->get_declaration_or_implementation_locations) if(view->get_declaration_or_implementation_locations)
goto_selected_location(view, view->get_declaration_or_implementation_locations(Notebook::get().get_views())); goto_selected_location(view, view->get_declaration_or_implementation_locations());
} }
}); });
menu.add_action("source_goto_usage", [this]() { menu.add_action("source_goto_usage", [this]() {
if(auto view=Notebook::get().get_current_view()) { if(auto view=Notebook::get().get_current_view()) {
if(view->get_usages) { if(view->get_usages) {
auto usages=view->get_usages(Notebook::get().get_views()); auto usages=view->get_usages();
if(!usages.empty()) { if(!usages.empty()) {
auto dialog_iter=view->get_iter_for_dialog(); auto dialog_iter=view->get_iter_for_dialog();
SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true); SelectionDialog::create(view, view->get_buffer()->create_mark(dialog_iter), true, true);
@ -917,7 +921,7 @@ void Window::set_menu_actions() {
Notebook::get().open(offset.file_path); Notebook::get().open(offset.file_path);
auto view=Notebook::get().get_current_view(); auto view=Notebook::get().get_current_view();
view->place_cursor_at_line_index(offset.line, offset.index); view->place_cursor_at_line_index(offset.line, offset.index);
view->scroll_to(view->get_buffer()->get_insert(), 0.0, 1.0, 0.5); view->scroll_to_cursor_delayed(view, true, false);
view->hide_tooltips(); view->hide_tooltips();
}; };
view->hide_tooltips(); view->hide_tooltips();
@ -1555,21 +1559,12 @@ void Window::rename_token_entry() {
if(view->get_token_spelling && view->rename_similar_tokens) { if(view->get_token_spelling && view->rename_similar_tokens) {
auto spelling=std::make_shared<std::string>(view->get_token_spelling()); auto spelling=std::make_shared<std::string>(view->get_token_spelling());
if(!spelling->empty()) { if(!spelling->empty()) {
EntryBox::get().labels.emplace_back();
auto label_it=EntryBox::get().labels.begin();
label_it->update=[label_it](int state, const std::string& message){
label_it->set_text("Warning: only opened files will be refactored, and altered files will be saved");
};
label_it->update(0, "");
auto iter=std::make_shared<Gtk::TextIter>(view->get_buffer()->get_insert()->get_iter()); auto iter=std::make_shared<Gtk::TextIter>(view->get_buffer()->get_insert()->get_iter());
EntryBox::get().entries.emplace_back(*spelling, [this, view, spelling, iter](const std::string& content){ EntryBox::get().entries.emplace_back(*spelling, [this, view, spelling, iter](const std::string& content){
//TODO: gtk needs a way to check if iter is valid without dumping g_error message //TODO: gtk needs a way to check if iter is valid without dumping g_error message
//iter->get_buffer() will print such a message, but no segfault will occur //iter->get_buffer() will print such a message, but no segfault will occur
if(Notebook::get().get_current_view()==view && content!=*spelling && iter->get_buffer() && view->get_buffer()->get_insert()->get_iter()==*iter) { if(Notebook::get().get_current_view()==view && content!=*spelling && iter->get_buffer() && view->get_buffer()->get_insert()->get_iter()==*iter)
auto renamed_pairs=view->rename_similar_tokens(Notebook::get().get_views(), content); view->rename_similar_tokens(content);
for(auto &renamed: renamed_pairs)
Terminal::get().print("Replaced "+std::to_string(renamed.second)+" occurrence"+(renamed.second>1?"s":"")+" in file "+renamed.first.string()+"\n");
}
else else
Info::get().print("Operation canceled"); Info::get().print("Operation canceled");
EntryBox::get().hide(); EntryBox::get().hide();

5
tests/CMakeLists.txt

@ -60,6 +60,11 @@ add_executable(terminal_test terminal_test.cc
target_link_libraries(terminal_test ${global_libraries}) target_link_libraries(terminal_test ${global_libraries})
add_test(terminal_test terminal_test) add_test(terminal_test terminal_test)
add_executable(usages_clang_test usages_clang_test.cc
$<TARGET_OBJECTS:project_shared> $<TARGET_OBJECTS:stubs>)
target_link_libraries(usages_clang_test ${global_libraries})
add_test(usages_clang_test usages_clang_test)
if(LIBLLDB_FOUND) if(LIBLLDB_FOUND)
add_executable(lldb_test lldb_test.cc add_executable(lldb_test lldb_test.cc
$<TARGET_OBJECTS:project_shared> $<TARGET_OBJECTS:stubs>) $<TARGET_OBJECTS:project_shared> $<TARGET_OBJECTS:stubs>)

8
tests/source_clang_test.cc

@ -40,7 +40,7 @@ int main() {
g_assert_cmpuint(location.line, ==, 6); g_assert_cmpuint(location.line, ==, 6);
clang_view->place_cursor_at_line_index(location.line, location.index); clang_view->place_cursor_at_line_index(location.line, location.index);
auto impl_locations=clang_view->get_implementation_locations({clang_view}); auto impl_locations=clang_view->get_implementation_locations();
g_assert_cmpuint(impl_locations.size(), ==, 1); g_assert_cmpuint(impl_locations.size(), ==, 1);
g_assert_cmpuint(impl_locations[0].line, ==, 11); g_assert_cmpuint(impl_locations[0].line, ==, 11);
@ -49,7 +49,7 @@ int main() {
g_assert_cmpuint(location.line, ==, 6); g_assert_cmpuint(location.line, ==, 6);
//test get_usages and get_methods //test get_usages and get_methods
auto locations=clang_view->get_usages({clang_view}); auto locations=clang_view->get_usages();
g_assert_cmpuint(locations.size(), >, 0); g_assert_cmpuint(locations.size(), >, 0);
locations=clang_view->get_methods(); locations=clang_view->get_methods();
@ -62,7 +62,7 @@ int main() {
g_assert_cmpstr(token.c_str(), ==, "TestClass"); g_assert_cmpstr(token.c_str(), ==, "TestClass");
location=clang_view->get_declaration_location(); location=clang_view->get_declaration_location();
g_assert_cmpuint(location.line, ==, 0); g_assert_cmpuint(location.line, ==, 0);
clang_view->rename_similar_tokens({clang_view}, "RenamedTestClass"); clang_view->rename_similar_tokens("RenamedTestClass");
while(!clang_view->parsed) while(!clang_view->parsed)
flush_events(); flush_events();
auto iter=clang_view->get_buffer()->get_insert()->get_iter(); auto iter=clang_view->get_buffer()->get_insert()->get_iter();
@ -71,7 +71,7 @@ int main() {
g_assert_cmpstr(token.c_str(), ==, "RenamedTestClass"); g_assert_cmpstr(token.c_str(), ==, "RenamedTestClass");
g_assert_cmpuint(clang_view->clang_diagnostics.size(), ==, 0); g_assert_cmpuint(clang_view->clang_diagnostics.size(), ==, 0);
clang_view->get_buffer()->set_text(saved_main); clang_view->get_buffer()->set_text(saved_main);
clang_view->save({clang_view}); clang_view->save();
//test error //test error
clang_view->get_buffer()->set_text(main_error); clang_view->get_buffer()->set_text(main_error);

2
tests/source_test.cc

@ -30,7 +30,7 @@ int main() {
{ {
Source::View source_view(source_file, Glib::RefPtr<Gsv::Language>()); Source::View source_view(source_file, Glib::RefPtr<Gsv::Language>());
source_view.get_buffer()->set_text(hello_world); source_view.get_buffer()->set_text(hello_world);
g_assert(source_view.save({&source_view})); g_assert(source_view.save());
} }
Source::View source_view(source_file, Glib::RefPtr<Gsv::Language>()); Source::View source_view(source_file, Glib::RefPtr<Gsv::Language>());

2
tests/stubs/dialogs.cc

@ -1,6 +1,6 @@
#include "dialogs.h" #include "dialogs.h"
Dialog::Message::Message(const std::string &text): Gtk::MessageDialog(text, false, Gtk::MessageType::MESSAGE_INFO, Gtk::ButtonsType::BUTTONS_NONE, true) {} Dialog::Message::Message(const std::string &text): Gtk::Window(Gtk::WindowType::WINDOW_POPUP) {}
bool Dialog::Message::on_delete_event(GdkEventAny *event) { bool Dialog::Message::on_delete_event(GdkEventAny *event) {
return true; return true;

273
tests/usages_clang_test.cc

@ -0,0 +1,273 @@
#include "clangmm.h"
#include "compile_commands.h"
#include "meson.h"
#include "project.h"
#include "usages_clang.h"
#include <cassert>
#include <fstream>
#include <iostream>
int main() {
auto tests_path = boost::filesystem::canonical(JUCI_TESTS_PATH);
auto project_path = boost::filesystem::canonical(tests_path / "usages_clang_test_files");
auto build_path = project_path / "build";
auto build = Project::Build::create(project_path);
g_assert(build->project_path == project_path);
{
clangmm::Index index(0, 0);
auto path = project_path / "main.cpp";
std::ifstream stream(path.string(), std::ifstream::binary);
assert(stream);
std::string buffer;
buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
auto arguments = CompileCommands::get_arguments(build_path, path);
clangmm::TranslationUnit translation_unit(index, path.string(), arguments, buffer);
auto tokens = translation_unit.get_tokens();
clangmm::Token *found_token = nullptr;
for(auto &token : *tokens) {
if(token.get_spelling() == "a") {
found_token = &token;
break;
}
}
assert(found_token);
auto spelling = found_token->get_spelling();
auto cursor = found_token->get_cursor().get_referenced();
std::vector<Usages::Clang::Usages> usages;
Usages::Clang::PathSet visited;
Usages::Clang::add_usages(project_path, build_path, boost::filesystem::path(), usages, visited, spelling, cursor, &translation_unit, false);
assert(usages.size() == 1);
assert(usages[0].path == path);
assert(usages[0].lines.size() == 1);
assert(usages[0].lines[0] == " test.a=2;");
assert(usages[0].offsets.size() == 1);
assert(usages[0].offsets[0].first.line == 6);
assert(usages[0].offsets[0].first.index == 8);
assert(usages[0].offsets[0].second.line == 6);
assert(usages[0].offsets[0].second.index == 9);
Usages::Clang::add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, &translation_unit, false);
assert(usages.size() == 2);
assert(usages[1].path == project_path / "test.hpp");
assert(usages[1].lines.size() == 2);
assert(usages[1].lines[0] == " int a=0;");
assert(usages[1].lines[1] == " ++a;");
assert(usages[1].offsets.size() == 2);
assert(usages[1].offsets[0].first.line == 6);
assert(usages[1].offsets[0].first.index == 7);
assert(usages[1].offsets[0].second.line == 6);
assert(usages[1].offsets[0].second.index == 8);
assert(usages[1].offsets[1].first.line == 8);
assert(usages[1].offsets[1].first.index == 7);
assert(usages[1].offsets[1].second.line == 8);
assert(usages[1].offsets[1].second.index == 8);
auto paths = Usages::Clang::find_paths(project_path, build_path, build_path / "debug");
auto pair = Usages::Clang::parse_paths(spelling, paths);
auto &paths_includes = pair.first;
assert(paths_includes.size() == 3);
assert(paths_includes.find(project_path / "main.cpp") != paths_includes.end());
assert(paths_includes.find(project_path / "test.hpp") != paths_includes.end());
assert(paths_includes.find(project_path / "test2.hpp") != paths_includes.end());
auto &paths_with_spelling = pair.second;
assert(paths_with_spelling.size() == 3);
assert(paths_with_spelling.find(project_path / "main.cpp") != paths_with_spelling.end());
assert(paths_with_spelling.find(project_path / "test.hpp") != paths_with_spelling.end());
assert(paths_with_spelling.find(project_path / "test2.hpp") != paths_with_spelling.end());
auto pair2 = Usages::Clang::find_potential_paths(cursor.get_canonical().get_source_location().get_path(), project_path, pair.first, pair.second);
auto &potential_paths = pair2.first;
assert(potential_paths.size() == 3);
assert(potential_paths.find(project_path / "main.cpp") != potential_paths.end());
assert(potential_paths.find(project_path / "test.hpp") != potential_paths.end());
assert(potential_paths.find(project_path / "test2.hpp") != potential_paths.end());
auto &all_includes = pair2.second;
assert(all_includes.size() == 1);
assert(*all_includes.begin() == project_path / "test.hpp");
// Remove visited paths
for(auto it = potential_paths.begin(); it != potential_paths.end();) {
if(visited.find(*it) != visited.end())
it = potential_paths.erase(it);
else
++it;
}
assert(potential_paths.size() == 1);
assert(*potential_paths.begin() == project_path / "test2.hpp");
{
auto path = *potential_paths.begin();
std::ifstream stream(path.string(), std::ifstream::binary);
std::string buffer;
buffer.assign(std::istreambuf_iterator<char>(stream), std::istreambuf_iterator<char>());
clangmm::TranslationUnit translation_unit(index, path.string(), arguments, buffer);
Usages::Clang::add_usages(project_path, build_path, path, usages, visited, spelling, cursor, &translation_unit, true);
Usages::Clang::add_usages_from_includes(project_path, build_path, usages, visited, spelling, cursor, &translation_unit, true);
assert(usages.size() == 3);
assert(usages[2].path == path);
assert(usages[2].lines.size() == 1);
assert(usages[2].lines[0] == " ++a;");
assert(usages[2].offsets.size() == 1);
assert(usages[2].offsets[0].first.line == 5);
assert(usages[2].offsets[0].first.index == 7);
assert(usages[2].offsets[0].second.line == 5);
assert(usages[2].offsets[0].second.index == 8);
}
assert(Usages::Clang::caches.size() == 1);
auto cache_it = Usages::Clang::caches.begin();
assert(cache_it->first == project_path / "test2.hpp");
assert(cache_it->second.build_path == build_path);
assert(cache_it->second.paths_and_last_write_times.size() == 2);
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test2.hpp") != cache_it->second.paths_and_last_write_times.end());
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test.hpp") != cache_it->second.paths_and_last_write_times.end());
assert(cache_it->second.tokens.size());
assert(cache_it->second.cursors.size());
{
std::vector<Usages::Clang::Usages> usages;
Usages::Clang::PathSet visited;
Usages::Clang::add_usages_from_cache(cache_it->first, usages, visited, spelling, cursor, cache_it->second);
assert(usages.size() == 1);
assert(usages[0].path == cache_it->first);
assert(usages[0].lines.size() == 1);
assert(usages[0].lines[0] == " ++a;");
assert(usages[0].offsets.size() == 1);
assert(usages[0].offsets[0].first.line == 5);
assert(usages[0].offsets[0].first.index == 7);
assert(usages[0].offsets[0].second.line == 5);
assert(usages[0].offsets[0].second.index == 8);
}
Usages::Clang::erase_unused_caches({project_path});
Usages::Clang::cache(project_path, build_path, path, time(nullptr), {project_path}, &translation_unit, tokens.get());
assert(Usages::Clang::caches.size() == 3);
assert(Usages::Clang::caches.find(project_path / "main.cpp") != Usages::Clang::caches.end());
assert(Usages::Clang::caches.find(project_path / "test.hpp") != Usages::Clang::caches.end());
assert(Usages::Clang::caches.find(project_path / "test2.hpp") != Usages::Clang::caches.end());
Usages::Clang::erase_unused_caches({});
Usages::Clang::cache(project_path, build_path, path, time(nullptr), {}, &translation_unit, tokens.get());
assert(Usages::Clang::caches.size() == 0);
auto cache = Usages::Clang::read_cache(project_path, build_path, project_path / "main.cpp");
assert(cache);
Usages::Clang::caches.emplace(project_path / "main.cpp", std::move(cache));
assert(!cache);
assert(Usages::Clang::caches.size() == 1);
cache = Usages::Clang::read_cache(project_path, build_path, project_path / "test.hpp");
assert(cache);
Usages::Clang::caches.emplace(project_path / "test.hpp", std::move(cache));
assert(!cache);
assert(Usages::Clang::caches.size() == 2);
cache = Usages::Clang::read_cache(project_path, build_path, project_path / "test2.hpp");
assert(cache);
Usages::Clang::caches.emplace(project_path / "test2.hpp", std::move(cache));
assert(!cache);
assert(Usages::Clang::caches.size() == 3);
cache = Usages::Clang::read_cache(project_path, build_path, project_path / "test_not_existing.hpp");
assert(!cache);
assert(Usages::Clang::caches.size() == 3);
{
auto cache_it = Usages::Clang::caches.find(project_path / "main.cpp");
assert(cache_it != Usages::Clang::caches.end());
assert(cache_it->first == project_path / "main.cpp");
assert(cache_it->second.build_path == build_path);
assert(cache_it->second.paths_and_last_write_times.size() == 2);
assert(cache_it->second.paths_and_last_write_times.find(project_path / "main.cpp") != cache_it->second.paths_and_last_write_times.end());
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test.hpp") != cache_it->second.paths_and_last_write_times.end());
assert(cache_it->second.tokens.size());
assert(cache_it->second.cursors.size());
{
std::vector<Usages::Clang::Usages> usages;
Usages::Clang::PathSet visited;
Usages::Clang::add_usages_from_cache(cache_it->first, usages, visited, spelling, cursor, cache_it->second);
assert(usages.size() == 1);
assert(usages[0].path == cache_it->first);
assert(usages[0].lines.size() == 1);
assert(usages[0].lines[0] == " test.a=2;");
assert(usages[0].offsets.size() == 1);
assert(usages[0].offsets[0].first.line == 6);
assert(usages[0].offsets[0].first.index == 8);
assert(usages[0].offsets[0].second.line == 6);
assert(usages[0].offsets[0].second.index == 9);
}
}
{
auto cache_it = Usages::Clang::caches.find(project_path / "test.hpp");
assert(cache_it != Usages::Clang::caches.end());
assert(cache_it->first == project_path / "test.hpp");
assert(cache_it->second.build_path == build_path);
assert(cache_it->second.paths_and_last_write_times.size() == 1);
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test.hpp") != cache_it->second.paths_and_last_write_times.end());
assert(cache_it->second.tokens.size());
assert(cache_it->second.cursors.size());
{
std::vector<Usages::Clang::Usages> usages;
Usages::Clang::PathSet visited;
Usages::Clang::add_usages_from_cache(cache_it->first, usages, visited, spelling, cursor, cache_it->second);
assert(usages.size() == 1);
assert(usages[0].path == cache_it->first);
assert(usages[0].lines.size() == 2);
assert(usages[0].lines[0] == " int a=0;");
assert(usages[0].lines[1] == " ++a;");
assert(usages[0].offsets.size() == 2);
assert(usages[0].offsets[0].first.line == 6);
assert(usages[0].offsets[0].first.index == 7);
assert(usages[0].offsets[0].second.line == 6);
assert(usages[0].offsets[0].second.index == 8);
assert(usages[0].offsets[1].first.line == 8);
assert(usages[0].offsets[1].first.index == 7);
assert(usages[0].offsets[1].second.line == 8);
assert(usages[0].offsets[1].second.index == 8);
}
}
{
auto cache_it = Usages::Clang::caches.find(project_path / "test2.hpp");
assert(cache_it != Usages::Clang::caches.end());
assert(cache_it->first == project_path / "test2.hpp");
assert(cache_it->second.build_path == build_path);
assert(cache_it->second.paths_and_last_write_times.size() == 2);
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test2.hpp") != cache_it->second.paths_and_last_write_times.end());
assert(cache_it->second.paths_and_last_write_times.find(project_path / "test.hpp") != cache_it->second.paths_and_last_write_times.end());
assert(cache_it->second.tokens.size());
assert(cache_it->second.cursors.size());
{
std::vector<Usages::Clang::Usages> usages;
Usages::Clang::PathSet visited;
Usages::Clang::add_usages_from_cache(cache_it->first, usages, visited, spelling, cursor, cache_it->second);
assert(usages.size() == 1);
assert(usages[0].path == cache_it->first);
assert(usages[0].lines.size() == 1);
assert(usages[0].lines[0] == " ++a;");
assert(usages[0].offsets.size() == 1);
assert(usages[0].offsets[0].first.line == 5);
assert(usages[0].offsets[0].first.index == 7);
assert(usages[0].offsets[0].second.line == 5);
assert(usages[0].offsets[0].second.index == 8);
}
}
}
{
assert(!Usages::Clang::caches.empty());
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder));
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"main.cpp.usages"));
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"test.hpp.usages"));
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"test2.hpp.usages"));
Usages::Clang::erase_all_caches_for_project(project_path, build_path);
assert(Usages::Clang::caches.empty());
assert(boost::filesystem::exists(build_path/Usages::Clang::cache_folder));
assert(!boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"main.cpp.usages"));
assert(!boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"test.hpp.usages"));
assert(!boost::filesystem::exists(build_path/Usages::Clang::cache_folder/"test2.hpp.usages"));
}
}

7
tests/usages_clang_test_files/build/compile_commands.json

@ -0,0 +1,7 @@
[
{
"directory": "jucipp/tests/usages_clang_test_files/build",
"command": "c++ -Ihello@exe -I. -I.. -Xclang -fcolor-diagnostics -pipe -Wall -Winvalid-pch -Wnon-virtual-dtor -O0 -g -std=c++11 -Wall -Wextra -MMD -MQ 'hello@exe/main.cpp.o' -MF 'hello@exe/main.cpp.o.d' -o 'hello@exe/main.cpp.o' -c ../main.cpp",
"file": "../main.cpp"
}
]

9
tests/usages_clang_test_files/main.cpp

@ -0,0 +1,9 @@
#include <iostream>
#include "test.hpp"
int main() {
Test test;
test.a=2;
test.b();
test.c();
}

5
tests/usages_clang_test_files/meson.build

@ -0,0 +1,5 @@
project('hello.world', 'cpp')
compiler_args = ['-std=c++11', '-Wall', '-Wextra']
executable('hello', 'main.cpp', cpp_args: compiler_args)

13
tests/usages_clang_test_files/test.hpp

@ -0,0 +1,13 @@
class Test {
public:
Test() {}
~Test() {}
int a=0;
void b() {
++a;
}
void c() {
b();
}
};

7
tests/usages_clang_test_files/test2.hpp

@ -0,0 +1,7 @@
#include "test.hpp"
class Test2 : public Test {
void d() {
++a;
}
};
Loading…
Cancel
Save