2018-08-21 05:27:52 +00:00
|
|
|
// Copyright 2017-2018 ccls Authors
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2018-10-29 04:21:21 +00:00
|
|
|
#include "include_complete.hh"
|
2017-05-21 19:51:15 +00:00
|
|
|
|
2018-04-08 00:10:54 +00:00
|
|
|
#include "filesystem.hh"
|
2018-10-29 04:21:21 +00:00
|
|
|
#include "match.hh"
|
|
|
|
#include "platform.hh"
|
2018-10-28 17:49:31 +00:00
|
|
|
#include "project.hh"
|
2017-05-21 23:48:21 +00:00
|
|
|
|
2018-05-27 19:24:56 +00:00
|
|
|
#include <llvm/ADT/Twine.h>
|
|
|
|
#include <llvm/Support/Threading.h>
|
2018-06-01 04:21:34 +00:00
|
|
|
#include <llvm/Support/Timer.h>
|
2018-10-28 17:49:31 +00:00
|
|
|
|
|
|
|
#include <unordered_set>
|
2018-05-27 19:24:56 +00:00
|
|
|
using namespace llvm;
|
|
|
|
|
2018-04-08 00:10:54 +00:00
|
|
|
#include <thread>
|
|
|
|
|
2018-10-28 17:49:31 +00:00
|
|
|
namespace ccls {
|
2017-05-21 19:51:15 +00:00
|
|
|
namespace {
|
|
|
|
|
2017-05-23 07:24:14 +00:00
|
|
|
struct CompletionCandidate {
|
|
|
|
std::string absolute_path;
|
|
|
|
lsCompletionItem completion_item;
|
|
|
|
};
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
std::string ElideLongPath(const std::string &path) {
|
2018-09-30 03:26:55 +00:00
|
|
|
if (g_config->completion.include.maxPathSize <= 0 ||
|
|
|
|
(int)path.size() <= g_config->completion.include.maxPathSize)
|
2017-05-21 21:01:52 +00:00
|
|
|
return path;
|
|
|
|
|
2018-09-30 03:26:55 +00:00
|
|
|
size_t start = path.size() - g_config->completion.include.maxPathSize;
|
2017-05-21 21:01:52 +00:00
|
|
|
return ".." + path.substr(start + 2);
|
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
size_t TrimCommonPathPrefix(const std::string &result,
|
|
|
|
const std::string &trimmer) {
|
2018-04-16 19:36:02 +00:00
|
|
|
#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();
|
2017-05-21 19:51:15 +00:00
|
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns true iff angle brackets should be used.
|
2018-10-08 05:02:28 +00:00
|
|
|
bool TrimPath(Project *project, std::string &path) {
|
|
|
|
size_t pos = 0;
|
2017-05-21 19:51:15 +00:00
|
|
|
bool angle = false;
|
2018-10-08 05:02:28 +00:00
|
|
|
for (auto &[root, folder] : project->root2folder) {
|
|
|
|
size_t pos1 = 0;
|
|
|
|
for (auto &search : folder.angle_search_list)
|
|
|
|
pos1 = std::max(pos1, TrimCommonPathPrefix(path, search));
|
|
|
|
if (pos1 > pos) {
|
|
|
|
pos = pos1;
|
2017-05-21 19:51:15 +00:00
|
|
|
angle = true;
|
|
|
|
}
|
|
|
|
|
2018-10-08 05:02:28 +00:00
|
|
|
pos1 = TrimCommonPathPrefix(path, root);
|
|
|
|
for (auto &search : folder.quote_search_list)
|
|
|
|
pos1 = std::max(pos1, TrimCommonPathPrefix(path, search));
|
|
|
|
if (pos1 > pos) {
|
|
|
|
pos = pos1;
|
|
|
|
angle = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
path = path.substr(pos);
|
2017-05-21 19:51:15 +00:00
|
|
|
return angle;
|
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
lsCompletionItem BuildCompletionItem(const std::string &path,
|
2018-10-15 08:26:13 +00:00
|
|
|
bool use_angle_brackets) {
|
2017-05-21 19:51:15 +00:00
|
|
|
lsCompletionItem item;
|
2018-04-04 06:05:41 +00:00
|
|
|
item.label = ElideLongPath(path);
|
2018-08-09 17:08:14 +00:00
|
|
|
item.detail = path; // the include path, used in de-duplicating
|
2018-10-06 22:23:23 +00:00
|
|
|
item.textEdit.newText = path;
|
2017-05-21 19:51:15 +00:00
|
|
|
item.insertTextFormat = lsInsertTextFormat::PlainText;
|
2018-02-09 08:55:53 +00:00
|
|
|
item.use_angle_brackets_ = use_angle_brackets;
|
2018-10-15 08:26:13 +00:00
|
|
|
item.kind = lsCompletionItemKind::File;
|
|
|
|
item.priority_ = 0;
|
2017-05-21 19:51:15 +00:00
|
|
|
return item;
|
|
|
|
}
|
2018-08-09 17:08:14 +00:00
|
|
|
} // namespace
|
2017-05-21 19:51:15 +00:00
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
IncludeComplete::IncludeComplete(Project *project)
|
2018-04-04 06:05:41 +00:00
|
|
|
: is_scanning(false), project_(project) {}
|
2017-05-21 19:51:15 +00:00
|
|
|
|
2017-05-27 04:21:00 +00:00
|
|
|
void IncludeComplete::Rescan() {
|
2017-05-21 19:51:15 +00:00
|
|
|
if (is_scanning)
|
|
|
|
return;
|
|
|
|
|
|
|
|
completion_items.clear();
|
2017-05-29 23:57:19 +00:00
|
|
|
absolute_path_to_completion_item.clear();
|
2018-02-09 08:55:53 +00:00
|
|
|
inserted_paths.clear();
|
2017-05-21 19:51:15 +00:00
|
|
|
|
2018-09-30 03:26:55 +00:00
|
|
|
if (!match_ && (g_config->completion.include.whitelist.size() ||
|
|
|
|
g_config->completion.include.blacklist.size()))
|
2018-08-09 17:08:14 +00:00
|
|
|
match_ =
|
2018-09-30 03:26:55 +00:00
|
|
|
std::make_unique<GroupMatch>(g_config->completion.include.whitelist,
|
|
|
|
g_config->completion.include.blacklist);
|
2017-05-21 19:51:15 +00:00
|
|
|
|
|
|
|
is_scanning = true;
|
2018-04-16 19:36:02 +00:00
|
|
|
std::thread([this]() {
|
2018-05-28 00:50:02 +00:00
|
|
|
set_thread_name("include");
|
2018-10-08 05:02:28 +00:00
|
|
|
std::unordered_set<std::string> angle_set, quote_set;
|
|
|
|
for (auto &[root, folder] : project_->root2folder) {
|
|
|
|
for (const std::string &search : folder.angle_search_list)
|
|
|
|
if (angle_set.insert(search).second)
|
|
|
|
InsertIncludesFromDirectory(search, true);
|
|
|
|
for (const std::string &search : folder.quote_search_list)
|
|
|
|
if (quote_set.insert(search).second)
|
|
|
|
InsertIncludesFromDirectory(search, false);
|
|
|
|
}
|
2017-05-21 19:51:15 +00:00
|
|
|
|
|
|
|
is_scanning = false;
|
2018-08-09 17:08:14 +00:00
|
|
|
})
|
|
|
|
.detach();
|
2017-05-21 19:51:15 +00:00
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
void IncludeComplete::InsertCompletionItem(const std::string &absolute_path,
|
|
|
|
lsCompletionItem &&item) {
|
2018-02-09 08:55:53 +00:00
|
|
|
if (inserted_paths.insert({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() ||
|
2018-03-20 00:30:21 +00:00
|
|
|
completion_items[it->second].detail.length() > item.detail.length()) {
|
2018-03-20 02:51:42 +00:00
|
|
|
absolute_path_to_completion_item[absolute_path] =
|
|
|
|
completion_items.size() - 1;
|
2018-03-20 00:30:21 +00:00
|
|
|
}
|
2018-02-09 08:55:53 +00:00
|
|
|
} else {
|
2018-08-09 17:08:14 +00:00
|
|
|
lsCompletionItem &inserted_item =
|
2018-02-22 07:34:32 +00:00
|
|
|
completion_items[inserted_paths[item.detail]];
|
2018-02-09 08:55:53 +00:00
|
|
|
// Update |use_angle_brackets_|, prefer quotes.
|
|
|
|
if (!item.use_angle_brackets_)
|
|
|
|
inserted_item.use_angle_brackets_ = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-08 05:02:28 +00:00
|
|
|
void IncludeComplete::AddFile(const std::string &path) {
|
|
|
|
if (!EndsWithAny(path, g_config->completion.include.suffixWhitelist))
|
2017-05-21 19:51:15 +00:00
|
|
|
return;
|
2018-10-08 05:02:28 +00:00
|
|
|
if (match_ && !match_->IsMatch(path))
|
2017-05-21 19:51:15 +00:00
|
|
|
return;
|
|
|
|
|
2018-10-08 05:02:28 +00:00
|
|
|
std::string trimmed_path = path;
|
|
|
|
bool use_angle_brackets = TrimPath(project_, trimmed_path);
|
2018-10-15 08:26:13 +00:00
|
|
|
lsCompletionItem item = BuildCompletionItem(trimmed_path, use_angle_brackets);
|
2017-05-29 23:57:19 +00:00
|
|
|
|
2018-02-09 08:55:53 +00:00
|
|
|
std::unique_lock<std::mutex> lock(completion_items_mutex, std::defer_lock);
|
|
|
|
if (is_scanning)
|
|
|
|
lock.lock();
|
2018-10-08 05:02:28 +00:00
|
|
|
InsertCompletionItem(path, std::move(item));
|
2017-05-21 19:51:15 +00:00
|
|
|
}
|
|
|
|
|
2017-09-22 01:14:57 +00:00
|
|
|
void IncludeComplete::InsertIncludesFromDirectory(std::string directory,
|
|
|
|
bool use_angle_brackets) {
|
2017-05-23 07:24:14 +00:00
|
|
|
directory = NormalizePath(directory);
|
|
|
|
EnsureEndsInSlash(directory);
|
2018-07-27 07:21:57 +00:00
|
|
|
if (match_ && !match_->IsMatch(directory))
|
2018-02-06 18:53:31 +00:00
|
|
|
return;
|
2018-07-27 07:21:57 +00:00
|
|
|
bool include_cpp = directory.find("include/c++") != std::string::npos;
|
2017-05-23 07:24:14 +00:00
|
|
|
|
|
|
|
std::vector<CompletionCandidate> results;
|
2017-09-22 01:14:57 +00:00
|
|
|
GetFilesInFolder(
|
|
|
|
directory, true /*recursive*/, false /*add_folder_to_path*/,
|
2018-07-27 07:21:57 +00:00
|
|
|
[&](const std::string &path) {
|
|
|
|
if (!include_cpp &&
|
2018-09-30 03:26:55 +00:00
|
|
|
!EndsWithAny(path, g_config->completion.include.suffixWhitelist))
|
2017-09-22 01:14:57 +00:00
|
|
|
return;
|
|
|
|
if (match_ && !match_->IsMatch(directory + path))
|
|
|
|
return;
|
|
|
|
|
|
|
|
CompletionCandidate candidate;
|
|
|
|
candidate.absolute_path = directory + path;
|
2018-04-04 06:05:41 +00:00
|
|
|
candidate.completion_item =
|
2018-10-15 08:26:13 +00:00
|
|
|
BuildCompletionItem(path, use_angle_brackets);
|
2017-09-22 01:14:57 +00:00
|
|
|
results.push_back(candidate);
|
|
|
|
});
|
2017-05-21 19:51:15 +00:00
|
|
|
|
|
|
|
std::lock_guard<std::mutex> lock(completion_items_mutex);
|
2018-08-09 17:08:14 +00:00
|
|
|
for (CompletionCandidate &result : results)
|
2018-02-09 08:55:53 +00:00
|
|
|
InsertCompletionItem(result.absolute_path,
|
|
|
|
std::move(result.completion_item));
|
2017-05-21 19:51:15 +00:00
|
|
|
}
|
|
|
|
|
2018-08-09 17:08:14 +00:00
|
|
|
std::optional<lsCompletionItem>
|
|
|
|
IncludeComplete::FindCompletionItemForAbsolutePath(
|
|
|
|
const std::string &absolute_path) {
|
2017-05-29 23:57:19 +00:00
|
|
|
std::lock_guard<std::mutex> lock(completion_items_mutex);
|
|
|
|
|
|
|
|
auto it = absolute_path_to_completion_item.find(absolute_path);
|
|
|
|
if (it == absolute_path_to_completion_item.end())
|
2018-03-31 03:16:33 +00:00
|
|
|
return std::nullopt;
|
2017-05-29 23:57:19 +00:00
|
|
|
return completion_items[it->second];
|
|
|
|
}
|
2018-10-28 17:49:31 +00:00
|
|
|
} // namespace ccls
|