ccls/src/include_complete.cc

201 lines
6.4 KiB
C++
Raw Normal View History

#include "include_complete.h"
2018-04-08 00:10:54 +00:00
#include "filesystem.hh"
#include "match.h"
#include "platform.h"
#include "project.h"
#include "timer.h"
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>
using namespace llvm;
2018-04-08 00:10:54 +00:00
#include <thread>
namespace {
struct CompletionCandidate {
std::string absolute_path;
lsCompletionItem completion_item;
};
2018-04-04 06:05:41 +00:00
std::string ElideLongPath(const std::string& path) {
if (g_config->completion.includeMaxPathSize <= 0)
2017-05-21 21:01:52 +00:00
return path;
2018-04-04 06:05:41 +00:00
if ((int)path.size() <= g_config->completion.includeMaxPathSize)
2017-05-21 21:01:52 +00:00
return path;
2018-04-04 06:05:41 +00:00
size_t start = path.size() - g_config->completion.includeMaxPathSize;
2017-05-21 21:01:52 +00:00
return ".." + path.substr(start + 2);
}
2017-09-22 01:14:57 +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();
#endif
return 0;
}
// Returns true iff angle brackets should be used.
2017-09-22 01:14:57 +00:00
bool TrimPath(Project* project,
const std::string& project_root,
std::string* insert_path) {
2018-04-16 19:36:02 +00:00
size_t start = TrimCommonPathPrefix(*insert_path, project_root);
bool angle = false;
2018-04-16 19:36:02 +00:00
for (auto& include_dir : project->quote_include_directories)
start = std::max(start, TrimCommonPathPrefix(*insert_path, include_dir));
for (auto& include_dir : project->angle_include_directories) {
2018-04-16 19:36:02 +00:00
auto len = TrimCommonPathPrefix(*insert_path, include_dir);
if (len > start) {
start = len;
angle = true;
}
}
*insert_path = insert_path->substr(start);
return angle;
}
2018-04-04 06:05:41 +00:00
lsCompletionItem BuildCompletionItem(const std::string& path,
2018-02-09 08:55:53 +00:00
bool use_angle_brackets,
2017-09-22 01:14:57 +00:00
bool is_stl) {
lsCompletionItem item;
2018-04-04 06:05:41 +00:00
item.label = ElideLongPath(path);
2018-02-22 07:34:32 +00:00
item.detail = path; // the include path, used in de-duplicating
2017-05-21 21:01:52 +00:00
item.textEdit = lsTextEdit();
2018-01-29 14:26:13 +00:00
item.textEdit->newText = path;
item.insertTextFormat = lsInsertTextFormat::PlainText;
2018-02-09 08:55:53 +00:00
item.use_angle_brackets_ = use_angle_brackets;
2018-01-29 14:26:13 +00:00
if (is_stl) {
item.kind = lsCompletionItemKind::Module;
2018-01-29 14:26:13 +00:00
item.priority_ = 2;
} else {
item.kind = lsCompletionItemKind::File;
2018-01-29 14:26:13 +00:00
item.priority_ = 1;
}
return item;
}
} // namespace
2018-04-04 06:05:41 +00:00
IncludeComplete::IncludeComplete(Project* project)
: is_scanning(false), project_(project) {}
void IncludeComplete::Rescan() {
if (is_scanning)
return;
completion_items.clear();
absolute_path_to_completion_item.clear();
2018-02-09 08:55:53 +00:00
inserted_paths.clear();
2018-04-04 06:05:41 +00:00
if (!match_ && (g_config->completion.includeWhitelist.size() ||
g_config->completion.includeBlacklist.size()))
match_ = std::make_unique<GroupMatch>(g_config->completion.includeWhitelist,
g_config->completion.includeBlacklist);
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");
Timer timer;
for (const std::string& dir : project_->quote_include_directories)
InsertIncludesFromDirectory(dir, false /*use_angle_brackets*/);
for (const std::string& dir : project_->angle_include_directories)
InsertIncludesFromDirectory(dir, true /*use_angle_brackets*/);
timer.ResetAndPrint("[perf] Scanning for includes");
is_scanning = false;
2018-04-16 19:36:02 +00:00
}).detach();
}
2018-02-09 08:55:53 +00:00
void IncludeComplete::InsertCompletionItem(const std::string& absolute_path,
lsCompletionItem&& item) {
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() ||
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-02-09 08:55:53 +00:00
} else {
2018-02-22 07:34:32 +00:00
lsCompletionItem& inserted_item =
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;
}
}
void IncludeComplete::AddFile(const std::string& absolute_path) {
2018-04-04 06:05:41 +00:00
if (!EndsWithAny(absolute_path, g_config->completion.includeSuffixWhitelist))
return;
if (match_ && !match_->IsMatch(absolute_path))
return;
std::string trimmed_path = absolute_path;
2017-09-22 01:14:57 +00:00
bool use_angle_brackets =
2018-04-04 06:05:41 +00:00
TrimPath(project_, g_config->projectRoot, &trimmed_path);
lsCompletionItem item =
BuildCompletionItem(trimmed_path, use_angle_brackets, false /*is_stl*/);
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();
InsertCompletionItem(absolute_path, std::move(item));
}
2017-09-22 01:14:57 +00:00
void IncludeComplete::InsertIncludesFromDirectory(std::string directory,
bool use_angle_brackets) {
directory = NormalizePath(directory);
EnsureEndsInSlash(directory);
if (match_ && !match_->IsMatch(directory)) {
// Don't even enter the directory if it fails the patterns.
return;
}
std::vector<CompletionCandidate> results;
2017-09-22 01:14:57 +00:00
GetFilesInFolder(
directory, true /*recursive*/, false /*add_folder_to_path*/,
[&](const std::string& path) {
2018-04-04 06:05:41 +00:00
if (!EndsWithAny(path, g_config->completion.includeSuffixWhitelist))
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 =
BuildCompletionItem(path, use_angle_brackets, false /*is_stl*/);
2017-09-22 01:14:57 +00:00
results.push_back(candidate);
});
std::lock_guard<std::mutex> lock(completion_items_mutex);
2018-02-09 08:55:53 +00:00
for (CompletionCandidate& result : results)
InsertCompletionItem(result.absolute_path,
std::move(result.completion_item));
}
2018-03-31 03:16:33 +00:00
std::optional<lsCompletionItem> IncludeComplete::FindCompletionItemForAbsolutePath(
2017-09-22 01:14:57 +00:00
const std::string& absolute_path) {
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;
return completion_items[it->second];
}