diff --git a/.gitignore b/.gitignore index d03bf98..8448ec2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ !debian/* build +.usages_clang diff --git a/CMakeLists.txt b/CMakeLists.txt index dd2fbfd..9b552b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required (VERSION 2.8.8) project(juci) -set(JUCI_VERSION "1.2.5") +set(JUCI_VERSION "1.3.0-rc1") set(CPACK_PACKAGE_NAME "jucipp") set(CPACK_PACKAGE_CONTACT "Ole Christian Eidheim ") @@ -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_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") 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_SHLIBDEPS ON) include(CPack) @@ -57,7 +57,7 @@ else() message("liblldb not found. Building juCi++ without debugging support") endif() -find_package(Boost 1.54 COMPONENTS system filesystem REQUIRED) +find_package(Boost 1.54 COMPONENTS system filesystem serialization REQUIRED) find_package(ASPELL REQUIRED) include(FindPkgConfig) pkg_check_modules(GTKMM gtkmm-3.0 REQUIRED) diff --git a/README.md b/README.md index bf64c15..0a6824a 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ See [enhancements](https://github.com/cppit/jucipp/labels/enhancement) for plann ## Dependencies * boost-filesystem +* boost-serialization * gtkmm-3.0 * gtksourceviewmm-3.0 * aspell diff --git a/debian/control b/debian/control index 1e69ddd..7e31e09 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: jucipp Section: unknown Priority: optional Maintainer: Ole Christian Eidheim -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 Homepage: https://github.com/cppit/jucipp diff --git a/docs/install.md b/docs/install.md index 7d4d50e..a57ad2b 100644 --- a/docs/install.md +++ b/docs/install.md @@ -17,7 +17,7 @@ ## Debian/Linux Mint/Ubuntu Install dependencies: ```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: diff --git a/libclangmm b/libclangmm index 955c113..b242f8d 160000 --- a/libclangmm +++ b/libclangmm @@ -1 +1 @@ -Subproject commit 955c113c73ecdfbf5fecc1c0b8fef537693c2f25 +Subproject commit b242f8d6a73cb211ea9faa9bbfd4f444e0afbf0a diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fe8d28e..1c54ffe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,6 +33,7 @@ set(project_shared_files source_diff.cc source_spellcheck.cc terminal.cc + usages_clang.cc ${PROJECT_SOURCE_DIR}/libclangmm/src/CodeCompleteResults.cc ${PROJECT_SOURCE_DIR}/libclangmm/src/CompilationDatabase.cc diff --git a/src/config.cc b/src/config.cc index 10c97f7..c4285a6 100644 --- a/src/config.cc +++ b/src/config.cc @@ -176,6 +176,7 @@ void Config::read(const boost::property_tree::ptree &cfg) { catch(const std::exception &) {} } source.clang_format_style = source_json.get("clang_format_style"); + source.clang_usages_threads = static_cast(source_json.get("clang_usages_threads")); auto pt_doc_search=cfg.get_child("documentation_searches"); for(auto &pt_doc_search_lang: pt_doc_search) { source.documentation_searches[pt_doc_search_lang.first].separator=pt_doc_search_lang.second.get("separator"); diff --git a/src/config.h b/src/config.h index 4f5cdee..2728c59 100644 --- a/src/config.h +++ b/src/config.h @@ -86,8 +86,10 @@ public: bool wrap_lines; bool highlight_current_line; bool show_line_numbers; + std::unordered_map clang_types; std::string clang_format_style; + unsigned clang_usages_threads; std::unordered_map documentation_searches; }; diff --git a/src/dialogs.cc b/src/dialogs.cc index af4549b..fd29e31 100644 --- a/src/dialogs.cc +++ b/src/dialogs.cc @@ -1,13 +1,25 @@ #include "dialogs.h" #include -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 gio_application=Glib::wrap(g_application, true); auto application=Glib::RefPtr::cast_static(gio_application); set_transient_for(*application->get_active_window()); + 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(); while(Gtk::Main::events_pending()) diff --git a/src/dialogs.h b/src/dialogs.h index d263139..f93d3ff 100644 --- a/src/dialogs.h +++ b/src/dialogs.h @@ -13,7 +13,7 @@ public: static std::string new_folder(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: Message(const std::string &text); protected: diff --git a/src/files.h b/src/files.h index 2ea9286..2c8cabd 100644 --- a/src/files.h +++ b/src/files.h @@ -76,7 +76,9 @@ R"RAW( "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": "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": { "history_size": 1000, diff --git a/src/filesystem.cc b/src/filesystem.cc index 572ba59..d04ff96 100644 --- a/src/filesystem.cc +++ b/src/filesystem.cc @@ -5,8 +5,6 @@ #include "filesystem.h" -const size_t buffer_size=131072; - //Only use on small files std::string filesystem::read(const std::string &path) { std::stringstream ss; @@ -18,80 +16,6 @@ std::string filesystem::read(const std::string &path) { return ss.str(); } -int filesystem::read(const std::string &path, Glib::RefPtr 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 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 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 std::vector filesystem::read_lines(const std::string &path) { std::vector res; @@ -114,28 +38,6 @@ bool filesystem::write(const std::string &path, const std::string &new_content) return true; } -bool filesystem::write(const std::string &path, Glib::RefPtr 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;cget_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) { auto escaped=argument; for(size_t pos=0;pos #include #include -#include class filesystem { public: static std::string read(const std::string &path); static std::string read(const boost::filesystem::path &path) { return read(path.string()); } - static int read(const std::string &path, Glib::RefPtr text_buffer); - static int read(const boost::filesystem::path &path, Glib::RefPtr text_buffer) { return read(path.string(), text_buffer); } - - static int read_non_utf8(const std::string &path, Glib::RefPtr text_buffer); - static int read_non_utf8(const boost::filesystem::path &path, Glib::RefPtr text_buffer) { return read_non_utf8(path.string(), text_buffer); } static std::vector read_lines(const std::string &path); static std::vector 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 std::string &path) { return write(path, ""); }; static bool write(const boost::filesystem::path &path) { return write(path, ""); }; - static bool write(const std::string &path, Glib::RefPtr text_buffer); - static bool write(const boost::filesystem::path &path, Glib::RefPtr text_buffer) { return write(path.string(), text_buffer); } static std::string escape_argument(const std::string &argument); static std::string unescape_argument(const std::string &argument); diff --git a/src/notebook.cc b/src/notebook.cc index 7a83a6b..021d853 100644 --- a/src/notebook.cc +++ b/src/notebook.cc @@ -422,7 +422,7 @@ void Notebook::configure(size_t index) { } bool Notebook::save(size_t index) { - if(!source_views[index]->save(source_views)) + if(!source_views[index]->save()) return false; Project::on_save(index); return true; diff --git a/src/project.cc b/src/project.cc index 19ca888..7c16081 100644 --- a/src/project.cc +++ b/src/project.cc @@ -11,6 +11,7 @@ #include "debug_lldb.h" #endif #include "info.h" +#include "usages_clang.h" boost::filesystem::path Project::debug_last_stop_file_path; std::unordered_map Project::run_arguments; @@ -61,7 +62,9 @@ void Project::on_save(size_t index) { auto build=Build::create(build_path); if(dynamic_cast(build.get()) || dynamic_cast(build.get())) { 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); for(size_t c=0;c buffer) { ////////////// //// View //// ////////////// +std::unordered_set Source::View::non_deleted_views; +std::map Source::View::views; + Source::View::View(const boost::filesystem::path &file_path, Glib::RefPtr 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(); 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()); bool status=true; if(language) { - if(filesystem::read_non_utf8(file_path, get_buffer())==-1) - Terminal::get().print("Warning: "+file_path.string()+" is not a valid UTF-8 file. Saving might corrupt the file.\n"); + std::ifstream input(file_path.string(), std::ofstream::binary); + 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 { - if(filesystem::read(file_path, get_buffer())==-1) { - Terminal::get().print("Error: "+file_path.string()+" is not a valid UTF-8 file.\n", true); + std::ifstream input(file_path.string(), std::ofstream::binary); + 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; } } @@ -599,7 +634,7 @@ Gsv::DrawSpacesFlags Source::View::parse_show_whitespace_characters(const std::s static_cast(std::accumulate(out.begin(), out.end(), 0)); } -bool Source::View::save(const std::vector &views) { +bool Source::View::save() { if(file_path.empty() || !get_buffer()->get_modified()) return false; if(Config::get().source.cleanup_whitespace_characters) @@ -612,7 +647,21 @@ bool Source::View::save(const std::vector &views) { 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; last_write_time=boost::filesystem::last_write_time(file_path, ec); if(ec) @@ -866,6 +915,9 @@ Source::View::~View() { delayed_tooltips_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) { diff --git a/src/source.h b/src/source.h index f55ccf3..4999f9a 100644 --- a/src/source.h +++ b/src/source.h @@ -40,13 +40,16 @@ namespace Source { class View : public SpellCheckView, public DiffView { public: + static std::unordered_set non_deleted_views; + static std::map views; + View(const boost::filesystem::path &file_path, Glib::RefPtr language); ~View(); bool load(); void rename(const boost::filesystem::path &path); - virtual bool save(const std::vector &views); + virtual bool save(); ///Set new text without moving scrolled window void replace_text(const std::string &text); @@ -67,14 +70,14 @@ namespace Source { std::function non_interactive_completion; std::function format_style; std::function get_declaration_location; - std::function(const std::vector &views)> get_implementation_locations; - std::function(const std::vector &views)> get_declaration_or_implementation_locations; - std::function >(const std::vector &views)> get_usages; + std::function()> get_implementation_locations; + std::function()> get_declaration_or_implementation_locations; + std::function >()> get_usages; std::function get_method; std::function >()> get_methods; std::function()> get_token_data; std::function get_token_spelling; - std::function >(const std::vector &views, const std::string &text)> rename_similar_tokens; + std::function rename_similar_tokens; std::function goto_next_diagnostic; std::function()> get_fix_its; std::function toggle_comments; @@ -103,7 +106,7 @@ namespace Source { bool soft_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;} protected: bool parsed=false; diff --git a/src/source_clang.cc b/src/source_clang.cc index 16fdc34..cf135c5 100644 --- a/src/source_clang.cc +++ b/src/source_clang.cc @@ -11,11 +11,14 @@ #include "selection_dialog.h" #include "filesystem.h" #include "compile_commands.h" +#include "usages_clang.h" clangmm::Index Source::ClangViewParse::clang_index(0, 0); Source::ClangViewParse::ClangViewParse(const boost::filesystem::path &file_path, Glib::RefPtr language): Source::View(file_path, language) { + Usages::Clang::erase_cache(file_path); + auto tag_table=get_buffer()->get_tag_table(); for (auto &item : Config::get().source.clang_types) { if(!tag_table->lookup(item.second)) { @@ -28,17 +31,17 @@ Source::ClangViewParse::ClangViewParse(const boost::filesystem::path &file_path, parse_initialize(); get_buffer()->signal_changed().connect([this]() { - soft_reparse(); + soft_reparse(true); }); } -bool Source::ClangViewParse::save(const std::vector &views) { - if(!Source::View::save(views)) +bool Source::ClangViewParse::save() { + if(!Source::View::save()) return false; if(language->get_id()=="chdr" || language->get_id()=="cpphdr") { for(auto &view: views) { - if(auto clang_view=dynamic_cast(view)) { + if(auto clang_view=dynamic_cast(view.second)) { if(this!=clang_view) clang_view->soft_reparse_needed=true; } @@ -94,7 +97,7 @@ void Source::ClangViewParse::parse_initialize() { pos++; } auto &buffer_raw=const_cast(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); auto build=Project::Build::create(file_path); @@ -103,7 +106,11 @@ void Source::ClangViewParse::parse_initialize() { build->update_default(); auto arguments=CompileCommands::get_arguments(build->get_default_path(), file_path); clang_tu = std::make_unique(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(); status_state="parsing..."; @@ -134,12 +141,16 @@ void Source::ClangViewParse::parse_initialize() { auto &parse_thread_buffer_raw=const_cast(parse_thread_buffer.raw()); if(this->language && (this->language->get_id()=="chdr" || this->language->get_id()=="cpphdr")) 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"); if(status==0) { auto expected=ParseProcessState::PROCESSING; 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(); parse_lock.unlock(); 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; parsed=false; if(parse_state!=ParseState::PROCESSING) @@ -196,7 +207,7 @@ void Source::ClangViewParse::soft_reparse() { update_status_state(this); } return false; - }, 1000); + }, delayed?1000:0); } void Source::ClangViewParse::update_syntax() { @@ -215,23 +226,25 @@ void Source::ClangViewParse::update_syntax() { buffer->remove_tag_by_name(tag, buffer->begin(), buffer->end()); last_syntax_tags.clear(); - for (auto &token : *clang_tokens) { + for(size_t c=0;csize();++c) { + auto &token=(*clang_tokens)[c]; + auto &token_offsets=clang_tokens_offsets[c]; //if(token.get_kind()==clangmm::Token::Kind::Token_Punctuation) - //ranges.emplace_back(token.offsets, static_cast(token.get_cursor().get_kind())); + //ranges.emplace_back(token_offset, static_cast(token.get_cursor().get_kind())); auto token_kind=token.get_kind(); 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) { auto cursor_kind=token.get_cursor().get_kind(); if(cursor_kind==clangmm::Cursor::Kind::DeclRefExpr || cursor_kind==clangmm::Cursor::Kind::MemberRefExpr) cursor_kind=token.get_cursor().get_referenced().get_kind(); if(cursor_kind!=clangmm::Cursor::Kind::PreprocessingDirective) - apply_tag(token.offsets, static_cast(cursor_kind)); + apply_tag(token_offsets, static_cast(cursor_kind)); } else if(token_kind==clangmm::Token::Kind::Literal) - apply_tag(token.offsets, static_cast(clangmm::Cursor::Kind::StringLiteral)); + apply_tag(token_offsets, static_cast(clangmm::Cursor::Kind::StringLiteral)); 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(); for(size_t c=clang_tokens->size()-1;c!=static_cast(-1);--c) { auto &token=(*clang_tokens)[c]; + auto &token_offsets=clang_tokens_offsets[c]; 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 referenced=cursor.get_referenced(); if(referenced) { - 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 create_tooltip_buffer=[this, &token]() { + 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 create_tooltip_buffer=[this, &token, &token_offsets]() { 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"); 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(); 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 end=get_buffer()->get_iter_at_line_index(token.offsets.second.line-1, token.offsets.second.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 iter=start; while((*iter>='a' && *iter<='z') || (*iter>='A' && *iter<='Z') || (*iter>='0' && *iter<='9') || *iter=='_' || *iter=='.') { start=iter; @@ -436,7 +450,7 @@ Source::ClangViewAutocomplete::ClangViewAutocomplete(const boost::filesystem::pa }; autocomplete.reparse=[this] { - soft_reparse(); + soft_reparse(true); }; autocomplete.cancel_reparse=[this] { @@ -651,7 +665,7 @@ const std::unordered_map &Source::ClangViewAutocomplet Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file_path, Glib::RefPtr language) : Source::ClangViewParse(file_path, language) { 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]() { if(last_tagged_identifier) { @@ -681,82 +695,116 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file return identifier.spelling; }; - rename_similar_tokens=[this](const std::vector &views, const std::string &text) { - std::vector > renamed; + rename_similar_tokens=[this](const std::string &text) { if(!parsed) { Info::get().print("Buffer is parsing"); - return renamed; + return; } auto identifier=get_identifier(); if(identifier) { - wait_parsing(views); + wait_parsing(); - //If rename constructor or destructor, set token to class - if(identifier.kind==clangmm::Cursor::Kind::Constructor || identifier.kind==clangmm::Cursor::Kind::Destructor) { - auto parent_cursor=identifier.cursor.get_semantic_parent(); - identifier=Identifier(identifier.spelling, parent_cursor); - } - //Special case for class with constructor template - 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); + std::vector translation_units; + translation_units.emplace_back(clang_tu.get()); + for(auto &view: views) { + if(view.second!=this) { + if(auto clang_view=dynamic_cast(view.second)) + translation_units.emplace_back(clang_view->clang_tu.get()); + } } + 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 renamed_views; - for(auto &view: views) { - if(auto clang_view=dynamic_cast(view)) { - //If rename class, also rename constructors and destructor - std::set identifiers; - identifiers.emplace(identifier); - auto identifier_cursor_kind=identifier.cursor.get_kind(); - if(identifier_cursor_kind==clangmm::Cursor::Kind::ClassDecl || identifier_cursor_kind==clangmm::Cursor::Kind::ClassTemplate || - identifier_cursor_kind==clangmm::Cursor::Kind::StructDecl) { - for(auto &token: *clang_view->clang_tokens) { - auto cursor=token.get_cursor(); - auto kind=cursor.get_kind(); - if((kind==clangmm::Cursor::Kind::Constructor || kind==clangmm::Cursor::Kind::Destructor || - kind==clangmm::Cursor::Kind::FunctionTemplate) && token.is_identifier()) { - auto parent_cursor=cursor.get_semantic_parent(); - if(parent_cursor.get_kind()==identifier.kind && token.get_spelling()==identifier.spelling && parent_cursor.get_usr_extended()==identifier.usr_extended) { - identifiers.emplace(token.get_spelling(), cursor); - } + std::vector usages_renamed; + for(auto &usage: usages) { + size_t line_c=usage.lines.size()-1; + auto view_it=views.find(usage.path); + if(view_it!=views.end()) { + view_it->second->get_buffer()->begin_user_action(); + for(auto offset_it=usage.offsets.rbegin();offset_it!=usage.offsets.rend();++offset_it) { + auto start_iter=view_it->second->get_buffer()->get_iter_at_line_index(offset_it->first.line-1, offset_it->first.index-1); + auto end_iter=view_it->second->get_buffer()->get_iter_at_line_index(offset_it->second.line-1, offset_it->second.index-1); + view_it->second->get_buffer()->erase(start_iter, end_iter); + start_iter=view_it->second->get_buffer()->get_iter_at_line_index(offset_it->first.line-1, offset_it->first.index-1); + view_it->second->get_buffer()->insert(start_iter, text); + if(offset_it->first.index-1first.index-1, offset_it->second.index-offset_it->first.index, text); + --line_c; + } + 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(stream), std::istreambuf_iterator()); + } + std::ofstream stream(usage.path.string(), std::ifstream::binary); + if(!buffer.empty() && stream) { + std::vector lines_start_pos={0}; + for(size_t c=0;cfirst.line-1; + auto end_line=offset_it->second.line-1; + if(start_linefirst.index-1; + auto end=lines_start_pos[end_line]+offset_it->second.index-1; + if(startfirst.index-1first.index-1, offset_it->second.index-offset_it->first.index, text); + --line_c; } + stream.write(buffer.data(), buffer.size()); + usages_renamed.emplace_back(&usage); } - - std::vector > offsets; - for(auto &identifier: identifiers) { - auto token_offsets=clang_view->clang_tokens->get_similar_token_offsets(identifier.kind, identifier.spelling, identifier.cursor.get_all_usr_extended()); - for(auto &token_offset: token_offsets) - offsets.emplace_back(token_offset); - } - std::vector, Glib::RefPtr > > marks; - for(auto &offset: offsets) { - 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)), - clang_view->get_buffer()->create_mark(clang_view->get_buffer()->get_iter_at_line_index(offset.second.line-1, offset.second.index-1))); + else + Terminal::get().print("Error: could not write to file "+usage.path.string()+'\n', true); + } + } + + if(!usages_renamed.empty()) { + Terminal::get().print("Renamed "); + Terminal::get().print(identifier.spelling, true); + Terminal::get().print(" to "); + Terminal::get().print(text, true); + Terminal::get().print(" at:\n"); + } + for(auto &usage: usages_renamed) { + size_t line_c=0; + 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()) { - clang_view->get_buffer()->begin_user_action(); - for(auto &mark: marks) { - clang_view->get_buffer()->erase(mark.first->get_iter(), mark.second->get_iter()); - 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()); + if(startsoft_reparse_needed=false; } - return renamed; }; get_buffer()->signal_mark_set().connect([this](const Gtk::TextBuffer::iterator& iterator, const Glib::RefPtr& mark){ @@ -850,16 +898,16 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file return offset; }; - auto implementation_locations=[this](const std::vector &views) { + auto implementation_locations=[this]() { std::vector offsets; auto identifier=get_identifier(); if(identifier) { - wait_parsing(views); + wait_parsing(); //First, look for a definition cursor that is equal auto identifier_usr=identifier.cursor.get_usr(); for(auto &view: views) { - if(auto clang_view=dynamic_cast(view)) { + if(auto clang_view=dynamic_cast(view.second)) { for(auto &token: *clang_view->clang_tokens) { auto cursor=token.get_cursor(); auto cursor_kind=cursor.get_kind(); @@ -936,12 +984,12 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file return offsets; }; - get_implementation_locations=[this, implementation_locations](const std::vector &views){ + get_implementation_locations=[this, implementation_locations](){ if(!parsed) { Info::get().print("Buffer is parsing"); return std::vector(); } - auto offsets=implementation_locations(views); + auto offsets=implementation_locations(); if(offsets.empty()) Info::get().print("No implementation found"); @@ -957,7 +1005,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file return offsets; }; - get_declaration_or_implementation_locations=[this, declaration_location, implementation_locations](const std::vector &views) { + get_declaration_or_implementation_locations=[this, declaration_location, implementation_locations]() { if(!parsed) { Info::get().print("Buffer is parsing"); return std::vector(); @@ -969,9 +1017,11 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file auto iter=get_buffer()->get_insert()->get_iter(); auto line=static_cast(iter.get_line()); auto index=static_cast(iter.get_line_index()); - for(auto &token: *clang_tokens) { + for(size_t c=0;csize();++c) { + auto &token=(*clang_tokens)[c]; 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) is_implementation=true; break; @@ -985,7 +1035,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file offsets.emplace_back(offset); } else { - auto implementation_offsets=implementation_locations(views); + auto implementation_offsets=implementation_locations(); if(!implementation_offsets.empty()) { offsets=std::move(implementation_offsets); } @@ -1011,7 +1061,7 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file return offsets; }; - get_usages=[this](const std::vector &views) { + get_usages=[this]() { std::vector > usages; if(!parsed) { Info::get().print("Buffer is parsing"); @@ -1019,49 +1069,53 @@ Source::ClangViewRefactor::ClangViewRefactor(const boost::filesystem::path &file } auto identifier=get_identifier(); if(identifier) { - wait_parsing(views); - std::vector views_reordered; - views_reordered.emplace_back(this); + wait_parsing(); + + 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, ""); + line.insert(token_start_pos, ""); + + size_t start_pos=0; + while(start_pos0) + line.erase(0, start_pos); + }; + + std::vector translation_units; + translation_units.emplace_back(clang_tu.get()); for(auto &view: views) { - if(view!=this) - views_reordered.emplace_back(view); + if(view.second!=this) { + if(auto clang_view=dynamic_cast(view.second)) + translation_units.emplace_back(clang_view->clang_tu.get()); + } } - for(auto &view: views_reordered) { - if(auto clang_view=dynamic_cast(view)) { - auto offsets=clang_view->clang_tokens->get_similar_token_offsets(identifier.kind, identifier.spelling, identifier.cursor.get_all_usr_extended()); - for(auto &offset: offsets) { - size_t whitespaces_removed=0; - auto start_iter=clang_view->get_buffer()->get_iter_at_line(offset.first.line-1); - while(!start_iter.ends_line() && (*start_iter==' ' || *start_iter=='\t')) { - start_iter.forward_char(); - 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, ""); - line.insert(token_start_pos, ""); - usages.emplace_back(Offset(offset.first.line-1, offset.first.index-1, clang_view->file_path), line); - } + + auto build=Project::Build::create(this->file_path); + 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 &usage: usages_clang) { + for(size_t c=0;cget_insert()->get_iter(); auto line=static_cast(iter.get_line()); auto index=static_cast(iter.get_line_index()); - for(auto &token: *clang_tokens) { + for(size_t c=0;csize();++c) { + auto &token=(*clang_tokens)[c]; 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 kind=cursor.get_kind(); 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"); return methods; } - clangmm::Offset last_offset(-1, -1); + clangmm::Offset last_offset{static_cast(-1), static_cast(-1)}; for(auto &token: *clang_tokens) { if(token.is_identifier()) { 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 line=static_cast(iter.get_line()); auto index=static_cast(iter.get_line_index()); - for(auto &token: *clang_tokens) { + for(size_t c=0;csize();++c) { + auto &token=(*clang_tokens)[c]; 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(); if(referenced) { 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(-1);--c) { auto &token=(*clang_tokens)[c]; 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(); if(referenced) return Identifier(token.get_spelling(), referenced); @@ -1393,11 +1452,11 @@ Source::ClangViewRefactor::Identifier Source::ClangViewRefactor::get_identifier( return Identifier(); } -void Source::ClangViewRefactor::wait_parsing(const std::vector &views) { +void Source::ClangViewRefactor::wait_parsing() { std::unique_ptr message; std::vector clang_views; for(auto &view: views) { - if(auto clang_view=dynamic_cast(view)) { + if(auto clang_view=dynamic_cast(view.second)) { if(!clang_view->parsed) { clang_views.emplace_back(clang_view); if(!message) @@ -1506,13 +1565,56 @@ void Source::ClangView::full_reparse() { } void Source::ClangView::async_delete() { - dispatcher.disconnect(); - delayed_reparse_connection.disconnect(); delayed_tag_similar_identifiers_connection.disconnect(); parsing_in_progress->cancel("canceled, freeing resources in the background"); - parse_state=ParseState::STOP; - delete_thread=std::thread([this](){ - //TODO: Is it possible to stop the clang-process in progress? + + views.erase(file_path); + std::set project_paths_in_use; + for(auto &view: views) { + if(dynamic_cast(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(stream), std::istreambuf_iterator()); + 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()) full_reparse_thread.join(); if(parse_thread.joinable()) diff --git a/src/source_clang.h b/src/source_clang.h index 26572d7..fbaa832 100644 --- a/src/source_clang.h +++ b/src/source_clang.h @@ -20,15 +20,16 @@ namespace Source { public: ClangViewParse(const boost::filesystem::path &file_path, Glib::RefPtr language); - bool save(const std::vector &views) override; + bool save() override; void configure() override; - void soft_reparse() override; + void soft_reparse(bool delayed=false) override; protected: Dispatcher dispatcher; void parse_initialize(); std::unique_ptr clang_tu; std::unique_ptr clang_tokens; + std::vector> clang_tokens_offsets; sigc::connection delayed_reparse_connection; std::shared_ptr parsing_in_progress; @@ -94,7 +95,7 @@ namespace Source { sigc::connection delayed_tag_similar_identifiers_connection; private: Identifier get_identifier(); - void wait_parsing(const std::vector &views); + void wait_parsing(); std::list, Glib::RefPtr > > similar_identifiers_marks; void tag_similar_identifiers(const Identifier &identifier); diff --git a/src/terminal.cc b/src/terminal.cc index 076a54a..ffbd850 100644 --- a/src/terminal.cc +++ b/src/terminal.cc @@ -3,6 +3,7 @@ #include "project.h" #include "info.h" #include "notebook.h" +#include "filesystem.h" #include Terminal::InProgress::InProgress(const std::string& start_msg): stop(false) { @@ -45,7 +46,7 @@ void Terminal::InProgress::cancel(const std::string& msg) { Terminal::Terminal() { 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->property_underline()=Pango::Underline::UNDERLINE_SINGLE; @@ -176,7 +177,7 @@ bool Terminal::on_motion_notify_event(GdkEventMotion *motion_event) { } std::tuple 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() "^[^:]*: ([A-Z]:)?([^:]+):([0-9]+): .* Assertion .* failed\\.$|" //gcc assert() "^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, "?"); } - 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) get_buffer()->insert_with_tag(get_buffer()->end(), umessage, bold_tag); else @@ -387,6 +388,18 @@ bool Terminal::on_button_press_event(GdkEventButton* button_event) { std::string line=std::get<3>(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(Project::current) { auto absolute_path=Project::current->build->get_default_path()/path; diff --git a/src/usages_clang.cc b/src/usages_clang.cc new file mode 100644 index 0000000..60a76bc --- /dev/null +++ b/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 +#include +#include +#include + +// #include //TODO: remove + +const boost::filesystem::path Usages::Clang::cache_folder = ".usages_clang"; +std::map Usages::Clang::caches; +std::mutex Usages::Clang::caches_mutex; +std::atomic 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(-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(-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 &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(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> Usages::Clang::Cache::get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling, + const std::unordered_set &usrs) const { + std::vector> offsets; + for(auto &token : tokens) { + if(token.cursor_id != static_cast(-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::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 &translation_units) { + std::vector 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 message; + const std::string message_string = "Please wait while finding usages"; + if(cache_in_progress_count != 0) { + message = std::make_unique(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 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(message_string); + + std::vector threads; + auto it = potential_paths.begin(); + auto number_of_threads = Config::get().source.clang_usages_threads; + if(number_of_threads == static_cast(-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 lock(mutex); + if(it == potential_paths.end()) + return; + path = *it; + ++it; + } + clangmm::Index index(0, 0); + + { + static std::mutex mutex; + std::unique_lock 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(stream), std::istreambuf_iterator()); + + 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 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(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 f; + ~ScopeExit() { + f(); + } + }; + ScopeExit scope_exit{[] { + --cache_in_progress_count; + }}; + + if(project_path.empty()) + return; + + { + std::unique_lock 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(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(-1) || ec) + continue; + auto tokens = translation_unit->get_tokens(path.string(), 0, file_size - 1); + std::unique_lock 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 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 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 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, PathSet &visited, const std::string &spelling, clangmm::Cursor cursor, + clangmm::TranslationUnit *translation_unit, bool store_in_cache) { + std::unique_ptr 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(-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 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 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, 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 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, 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(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, Usages::Clang::PathSet> Usages::Clang::parse_paths(const std::string &spelling, const PathSet &paths) { + std::map 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 &paths_includes) { + PathSet all_includes; + + class Recursive { + public: + static void f(PathSet &all_includes, const boost::filesystem::path &path, + const std::map &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::find_potential_paths(const boost::filesystem::path &path, const boost::filesystem::path &project_path, + const std::map &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(); +} diff --git a/src/usages_clang.h b/src/usages_clang.h new file mode 100644 index 0000000..8ba1705 --- /dev/null +++ b/src/usages_clang.h @@ -0,0 +1,151 @@ +#ifndef JUCI_USAGES_CLANG_H_ +#define JUCI_USAGES_CLANG_H_ +#include "clangmm.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { + namespace serialization { + template + 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 PathSet; + + class Usages { + public: + boost::filesystem::path path; + std::vector> offsets; + std::vector lines; + }; + + class Cache { + friend class boost::serialization::access; + template + 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 + void serialize(Archive &ar, const unsigned int version) { + ar &kind; + ar &usrs; + } + + public: + clangmm::Cursor::Kind kind; + std::unordered_set usrs; + + bool operator==(const Cursor &o); + }; + + class Token { + friend class boost::serialization::access; + template + 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 offsets; + size_t cursor_id; + }; + + boost::filesystem::path project_path; + boost::filesystem::path build_path; + + std::vector tokens; + std::vector cursors; + std::map 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> get_similar_token_offsets(clangmm::Cursor::Kind kind, const std::string &spelling, + const std::unordered_set &usrs) const; + }; + + private: + const static boost::filesystem::path cache_folder; + + static std::map caches; + static std::mutex caches_mutex; + + static std::atomic cache_in_progress_count; + + public: + static std::vector 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 &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, 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, 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, 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, PathSet> parse_paths(const std::string &spelling, const PathSet &paths); + + static PathSet get_all_includes(const boost::filesystem::path &path, const std::map &paths_includes); + + static std::pair find_potential_paths(const boost::filesystem::path &project_path, const boost::filesystem::path &include_path, + const std::map &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_ diff --git a/src/window.cc b/src/window.cc index a8b74ad..90a8160 100644 --- a/src/window.cc +++ b/src/window.cc @@ -217,6 +217,10 @@ void Window::set_menu_actions() { }); menu.add_action("quit", [this]() { close(); + while(!Source::View::non_deleted_views.empty()) { + while(Gtk::Main::events_pending()) + Gtk::Main::iteration(false); + } }); 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]() { if(auto view=Notebook::get().get_current_view()) { 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]() { if(auto view=Notebook::get().get_current_view()) { 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]() { if(auto view=Notebook::get().get_current_view()) { if(view->get_usages) { - auto usages=view->get_usages(Notebook::get().get_views()); + auto usages=view->get_usages(); if(!usages.empty()) { auto dialog_iter=view->get_iter_for_dialog(); 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); auto view=Notebook::get().get_current_view(); 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(); @@ -1555,21 +1559,12 @@ void Window::rename_token_entry() { if(view->get_token_spelling && view->rename_similar_tokens) { auto spelling=std::make_shared(view->get_token_spelling()); 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(view->get_buffer()->get_insert()->get_iter()); 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 //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) { - auto renamed_pairs=view->rename_similar_tokens(Notebook::get().get_views(), 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"); - } + if(Notebook::get().get_current_view()==view && content!=*spelling && iter->get_buffer() && view->get_buffer()->get_insert()->get_iter()==*iter) + view->rename_similar_tokens(content); else Info::get().print("Operation canceled"); EntryBox::get().hide(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 009dd6b..d07968b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -60,6 +60,11 @@ add_executable(terminal_test terminal_test.cc target_link_libraries(terminal_test ${global_libraries}) add_test(terminal_test terminal_test) +add_executable(usages_clang_test usages_clang_test.cc + $ $) +target_link_libraries(usages_clang_test ${global_libraries}) +add_test(usages_clang_test usages_clang_test) + if(LIBLLDB_FOUND) add_executable(lldb_test lldb_test.cc $ $) diff --git a/tests/source_clang_test.cc b/tests/source_clang_test.cc index 71190e0..3ae3b06 100644 --- a/tests/source_clang_test.cc +++ b/tests/source_clang_test.cc @@ -40,7 +40,7 @@ int main() { g_assert_cmpuint(location.line, ==, 6); 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[0].line, ==, 11); @@ -49,7 +49,7 @@ int main() { g_assert_cmpuint(location.line, ==, 6); //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); locations=clang_view->get_methods(); @@ -62,7 +62,7 @@ int main() { g_assert_cmpstr(token.c_str(), ==, "TestClass"); location=clang_view->get_declaration_location(); g_assert_cmpuint(location.line, ==, 0); - clang_view->rename_similar_tokens({clang_view}, "RenamedTestClass"); + clang_view->rename_similar_tokens("RenamedTestClass"); while(!clang_view->parsed) flush_events(); 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_cmpuint(clang_view->clang_diagnostics.size(), ==, 0); clang_view->get_buffer()->set_text(saved_main); - clang_view->save({clang_view}); + clang_view->save(); //test error clang_view->get_buffer()->set_text(main_error); diff --git a/tests/source_test.cc b/tests/source_test.cc index a3ab90c..d698334 100644 --- a/tests/source_test.cc +++ b/tests/source_test.cc @@ -30,7 +30,7 @@ int main() { { Source::View source_view(source_file, Glib::RefPtr()); 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()); diff --git a/tests/stubs/dialogs.cc b/tests/stubs/dialogs.cc index 7ed8fd0..d52373e 100644 --- a/tests/stubs/dialogs.cc +++ b/tests/stubs/dialogs.cc @@ -1,6 +1,6 @@ #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) { return true; diff --git a/tests/usages_clang_test.cc b/tests/usages_clang_test.cc new file mode 100644 index 0000000..84545bc --- /dev/null +++ b/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 +#include + +#include + +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(stream), std::istreambuf_iterator()); + 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; + 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(stream), std::istreambuf_iterator()); + 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; + 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; + 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; + 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; + 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")); + } +} diff --git a/tests/usages_clang_test_files/build/compile_commands.json b/tests/usages_clang_test_files/build/compile_commands.json new file mode 100644 index 0000000..e6d3bb3 --- /dev/null +++ b/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" + } +] diff --git a/tests/usages_clang_test_files/main.cpp b/tests/usages_clang_test_files/main.cpp new file mode 100644 index 0000000..8a40c85 --- /dev/null +++ b/tests/usages_clang_test_files/main.cpp @@ -0,0 +1,9 @@ +#include +#include "test.hpp" + +int main() { + Test test; + test.a=2; + test.b(); + test.c(); +} diff --git a/tests/usages_clang_test_files/meson.build b/tests/usages_clang_test_files/meson.build new file mode 100644 index 0000000..732fa50 --- /dev/null +++ b/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) diff --git a/tests/usages_clang_test_files/test.hpp b/tests/usages_clang_test_files/test.hpp new file mode 100644 index 0000000..2a79686 --- /dev/null +++ b/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(); + } +}; diff --git a/tests/usages_clang_test_files/test2.hpp b/tests/usages_clang_test_files/test2.hpp new file mode 100644 index 0000000..6117f9e --- /dev/null +++ b/tests/usages_clang_test_files/test2.hpp @@ -0,0 +1,7 @@ +#include "test.hpp" + +class Test2 : public Test { + void d() { + ++a; + } +};