// Copyright 2017-2018 ccls Authors // SPDX-License-Identifier: Apache-2.0 #include "include_complete.hh" #include "filesystem.hh" #include "platform.hh" #include "project.hh" #include #include #include #include using namespace llvm; #include namespace ccls { namespace { struct CompletionCandidate { std::string absolute_path; CompletionItem completion_item; }; std::string ElideLongPath(const std::string &path) { if (g_config->completion.include.maxPathSize <= 0 || (int)path.size() <= g_config->completion.include.maxPathSize) return path; size_t start = path.size() - g_config->completion.include.maxPathSize; return ".." + path.substr(start + 2); } size_t TrimCommonPathPrefix(const std::string &result, const std::string &trimmer) { #ifdef _WIN32 std::string s = result, t = trimmer; std::transform(s.begin(), s.end(), s.begin(), ::tolower); std::transform(t.begin(), t.end(), t.begin(), ::tolower); if (s.compare(0, t.size(), t) == 0) return t.size(); #else if (result.compare(0, trimmer.size(), trimmer) == 0) return trimmer.size(); #endif return 0; } int TrimPath(Project *project, std::string &path) { size_t pos = 0; int kind = 0; for (auto &[root, folder] : project->root2folder) for (auto &[search, search_dir_kind] : folder.search_dir2kind) if (int t = TrimCommonPathPrefix(path, search); t > pos) pos = t, kind = search_dir_kind; path = path.substr(pos); return kind; } CompletionItem BuildCompletionItem(const std::string &path, int kind) { CompletionItem item; item.label = ElideLongPath(path); item.detail = path; // the include path, used in de-duplicating item.textEdit.newText = path; item.insertTextFormat = InsertTextFormat::PlainText; item.kind = CompletionItemKind::File; item.quote_kind_ = kind; item.priority_ = 0; return item; } } // namespace IncludeComplete::IncludeComplete(Project *project) : is_scanning(false), project_(project) {} IncludeComplete::~IncludeComplete() { // Spin until the scanning has completed. while (is_scanning.load()) std::this_thread::sleep_for(std::chrono::milliseconds(100)); } void IncludeComplete::Rescan() { if (is_scanning || LLVM_VERSION_MAJOR >= 8) return; completion_items.clear(); absolute_path_to_completion_item.clear(); inserted_paths.clear(); if (!match_ && (g_config->completion.include.whitelist.size() || g_config->completion.include.blacklist.size())) match_ = std::make_unique(g_config->completion.include.whitelist, g_config->completion.include.blacklist); is_scanning = true; std::thread([this]() { set_thread_name("include"); for (auto &[root, folder] : project_->root2folder) { for (auto &search_kind : folder.search_dir2kind) { const std::string &search = search_kind.first; int kind = search_kind.second; assert(search.back() == '/'); if (match_ && !match_->Matches(search)) return; bool include_cpp = search.find("include/c++") != std::string::npos; std::vector results; GetFilesInFolder(search, true /*recursive*/, false /*add_folder_to_path*/, [&](const std::string &path) { bool ok = include_cpp; for (StringRef suffix : g_config->completion.include.suffixWhitelist) if (StringRef(path).endswith(suffix)) ok = true; if (!ok) return; if (match_ && !match_->Matches(search + path)) return; CompletionCandidate candidate; candidate.absolute_path = search + path; candidate.completion_item = BuildCompletionItem(path, kind); results.push_back(candidate); }); std::lock_guard lock(completion_items_mutex); for (CompletionCandidate &result : results) InsertCompletionItem(result.absolute_path, std::move(result.completion_item)); } } is_scanning = false; }) .detach(); } void IncludeComplete::InsertCompletionItem(const std::string &absolute_path, CompletionItem &&item) { if (inserted_paths.try_emplace(item.detail, inserted_paths.size()).second) { completion_items.push_back(item); // insert if not found or with shorter include path auto it = absolute_path_to_completion_item.find(absolute_path); if (it == absolute_path_to_completion_item.end() || completion_items[it->second].detail.length() > item.detail.length()) { absolute_path_to_completion_item[absolute_path] = completion_items.size() - 1; } } } void IncludeComplete::AddFile(const std::string &path) { bool ok = false; for (StringRef suffix : g_config->completion.include.suffixWhitelist) if (StringRef(path).endswith(suffix)) ok = true; if (!ok) return; if (match_ && !match_->Matches(path)) return; std::string trimmed_path = path; int kind = TrimPath(project_, trimmed_path); CompletionItem item = BuildCompletionItem(trimmed_path, kind); std::unique_lock lock(completion_items_mutex, std::defer_lock); if (is_scanning) lock.lock(); InsertCompletionItem(path, std::move(item)); } std::optional IncludeComplete::FindCompletionItemForAbsolutePath( const std::string &absolute_path) { std::lock_guard lock(completion_items_mutex); auto it = absolute_path_to_completion_item.find(absolute_path); if (it == absolute_path_to_completion_item.end()) return std::nullopt; return completion_items[it->second]; } } // namespace ccls