mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-25 17:11:59 +00:00
Make sure every include candidate is unique w.r.t. absolute path.
Also do not follow symlinks when iterating a directory.
This commit is contained in:
parent
ee90938b28
commit
37787290cb
@ -10,6 +10,11 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
struct CompletionCandidate {
|
||||||
|
std::string absolute_path;
|
||||||
|
lsCompletionItem completion_item;
|
||||||
|
};
|
||||||
|
|
||||||
std::string ElideLongPath(Config* config, const std::string& path) {
|
std::string ElideLongPath(Config* config, const std::string& path) {
|
||||||
if (config->includeCompletionMaximumPathLength <= 0)
|
if (config->includeCompletionMaximumPathLength <= 0)
|
||||||
return path;
|
return path;
|
||||||
@ -41,33 +46,33 @@ size_t TrimCommonPathPrefix(const std::string& result, const std::string& trimme
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns true iff angle brackets should be used.
|
// Returns true iff angle brackets should be used.
|
||||||
bool TrimPath(Project* project, const std::string& project_root, std::string& insert_path) {
|
bool TrimPath(Project* project, const std::string& project_root, std::string* insert_path) {
|
||||||
size_t start = 0;
|
size_t start = 0;
|
||||||
bool angle = false;
|
bool angle = false;
|
||||||
|
|
||||||
size_t len = TrimCommonPathPrefix(insert_path, project_root);
|
size_t len = TrimCommonPathPrefix(*insert_path, project_root);
|
||||||
if (len > start)
|
if (len > start)
|
||||||
start = len;
|
start = len;
|
||||||
|
|
||||||
for (auto& include_dir : project->quote_include_directories) {
|
for (auto& include_dir : project->quote_include_directories) {
|
||||||
len = TrimCommonPathPrefix(insert_path, include_dir);
|
len = TrimCommonPathPrefix(*insert_path, include_dir);
|
||||||
if (len > start)
|
if (len > start)
|
||||||
start = len;
|
start = len;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& include_dir : project->angle_include_directories) {
|
for (auto& include_dir : project->angle_include_directories) {
|
||||||
len = TrimCommonPathPrefix(insert_path, include_dir);
|
len = TrimCommonPathPrefix(*insert_path, include_dir);
|
||||||
if (len > start) {
|
if (len > start) {
|
||||||
start = len;
|
start = len;
|
||||||
angle = true;
|
angle = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
insert_path = insert_path.substr(start);
|
*insert_path = insert_path->substr(start);
|
||||||
return angle;
|
return angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
lsCompletionItem BuildCompletionItem(Config* config, std::string path, bool use_angle_brackets, bool is_stl) {
|
lsCompletionItem BuildCompletionItem(Config* config, const std::string& path, bool use_angle_brackets, bool is_stl) {
|
||||||
lsCompletionItem item;
|
lsCompletionItem item;
|
||||||
if (use_angle_brackets)
|
if (use_angle_brackets)
|
||||||
item.label = "#include <" + ElideLongPath(config, path) + ">";
|
item.label = "#include <" + ElideLongPath(config, path) + ">";
|
||||||
@ -103,6 +108,7 @@ void IncludeCompletion::Rescan() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
completion_items.clear();
|
completion_items.clear();
|
||||||
|
seen_paths.clear();
|
||||||
|
|
||||||
if (!match_ && (!config_->includeCompletionWhitelist.empty() || !config_->includeCompletionBlacklist.empty()))
|
if (!match_ && (!config_->includeCompletionWhitelist.empty() || !config_->includeCompletionBlacklist.empty()))
|
||||||
match_ = MakeUnique<GroupMatch>(config_->includeCompletionWhitelist, config_->includeCompletionBlacklist);
|
match_ = MakeUnique<GroupMatch>(config_->includeCompletionWhitelist, config_->includeCompletionBlacklist);
|
||||||
@ -124,44 +130,56 @@ void IncludeCompletion::Rescan() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void IncludeCompletion::AddFile(std::string path) {
|
void IncludeCompletion::AddFile(const std::string& absolute_path) {
|
||||||
if (!EndsWithAny(path, config_->includeCompletionWhitelistLiteralEnding))
|
if (!EndsWithAny(absolute_path, config_->includeCompletionWhitelistLiteralEnding))
|
||||||
return;
|
return;
|
||||||
if (match_ && !match_->IsMatch(path))
|
if (match_ && !match_->IsMatch(absolute_path))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool use_angle_brackets = TrimPath(project_, config_->projectRoot, path);
|
std::string trimmed_path = absolute_path;
|
||||||
lsCompletionItem item = BuildCompletionItem(config_, path, use_angle_brackets, false /*is_stl*/);
|
bool use_angle_brackets = TrimPath(project_, config_->projectRoot, &trimmed_path);
|
||||||
|
lsCompletionItem item = BuildCompletionItem(config_, trimmed_path, use_angle_brackets, false /*is_stl*/);
|
||||||
|
|
||||||
if (is_scanning) {
|
if (is_scanning) {
|
||||||
std::lock_guard<std::mutex> lock(completion_items_mutex);
|
std::lock_guard<std::mutex> lock(completion_items_mutex);
|
||||||
completion_items.insert(item);
|
if (seen_paths.insert(absolute_path).second)
|
||||||
|
completion_items.push_back(item);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
completion_items.insert(item);
|
if (seen_paths.insert(absolute_path).second)
|
||||||
|
completion_items.push_back(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IncludeCompletion::InsertIncludesFromDirectory(
|
void IncludeCompletion::InsertIncludesFromDirectory(
|
||||||
const std::string& directory,
|
std::string directory,
|
||||||
bool use_angle_brackets) {
|
bool use_angle_brackets) {
|
||||||
std::vector<lsCompletionItem> result;
|
directory = NormalizePath(directory);
|
||||||
|
EnsureEndsInSlash(directory);
|
||||||
|
|
||||||
|
std::vector<CompletionCandidate> results;
|
||||||
GetFilesInFolder(directory, true /*recursive*/, false /*add_folder_to_path*/, [&](const std::string& path) {
|
GetFilesInFolder(directory, true /*recursive*/, false /*add_folder_to_path*/, [&](const std::string& path) {
|
||||||
if (!EndsWithAny(path, config_->includeCompletionWhitelistLiteralEnding))
|
if (!EndsWithAny(path, config_->includeCompletionWhitelistLiteralEnding))
|
||||||
return;
|
return;
|
||||||
if (match_ && !match_->IsMatch(directory + path))
|
if (match_ && !match_->IsMatch(directory + path))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
result.push_back(BuildCompletionItem(config_, path, use_angle_brackets, false /*is_stl*/));
|
CompletionCandidate candidate;
|
||||||
|
candidate.absolute_path = directory + path;
|
||||||
|
candidate.completion_item = BuildCompletionItem(config_, path, use_angle_brackets, false /*is_stl*/);
|
||||||
|
results.push_back(candidate);
|
||||||
});
|
});
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(completion_items_mutex);
|
std::lock_guard<std::mutex> lock(completion_items_mutex);
|
||||||
completion_items.insert(result.begin(), result.end());
|
for (const CompletionCandidate& result : results) {
|
||||||
|
if (seen_paths.insert(result.absolute_path).second)
|
||||||
|
completion_items.push_back(result.completion_item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IncludeCompletion::InsertStlIncludes() {
|
void IncludeCompletion::InsertStlIncludes() {
|
||||||
std::lock_guard<std::mutex> lock(completion_items_mutex);
|
std::lock_guard<std::mutex> lock(completion_items_mutex);
|
||||||
for (const char* stl_header : kStandardLibraryIncludes) {
|
for (const char* stl_header : kStandardLibraryIncludes) {
|
||||||
completion_items.insert(BuildCompletionItem(config_, stl_header, true /*use_angle_brackets*/, true /*is_stl*/));
|
completion_items.push_back(BuildCompletionItem(config_, stl_header, true /*use_angle_brackets*/, true /*is_stl*/));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,17 +16,21 @@ struct IncludeCompletion {
|
|||||||
void Rescan();
|
void Rescan();
|
||||||
|
|
||||||
// Ensures the one-off file is inside |completion_items|.
|
// Ensures the one-off file is inside |completion_items|.
|
||||||
void AddFile(std::string path);
|
void AddFile(const std::string& absolute_path);
|
||||||
|
|
||||||
// Scans the given directory and inserts all includes from this. This is a
|
// Scans the given directory and inserts all includes from this. This is a
|
||||||
// blocking function and should be run off the querydb thread.
|
// blocking function and should be run off the querydb thread.
|
||||||
void InsertIncludesFromDirectory(const std::string& directory, bool use_angle_brackets);
|
void InsertIncludesFromDirectory(std::string directory, bool use_angle_brackets);
|
||||||
void InsertStlIncludes();
|
void InsertStlIncludes();
|
||||||
|
|
||||||
// Guards |completion_items| when |is_scanning| is true.
|
// Guards |completion_items| when |is_scanning| is true.
|
||||||
std::mutex completion_items_mutex;
|
std::mutex completion_items_mutex;
|
||||||
std::atomic<bool> is_scanning;
|
std::atomic<bool> is_scanning;
|
||||||
std::unordered_set<lsCompletionItem> completion_items;
|
std::vector<lsCompletionItem> completion_items;
|
||||||
|
// Paths inside of |completion_items|. Multiple paths could show up the same
|
||||||
|
// time, but with different bracket types, so we have to hash on the absolute
|
||||||
|
// path, and not what we insert into the completion results.
|
||||||
|
std::unordered_set<std::string> seen_paths;
|
||||||
|
|
||||||
// Cached references
|
// Cached references
|
||||||
Config* config_;
|
Config* config_;
|
||||||
|
@ -460,14 +460,6 @@ struct lsCompletionItem {
|
|||||||
// An data entry field that is preserved on a completion item between
|
// An data entry field that is preserved on a completion item between
|
||||||
// a completion and a completion resolve request.
|
// a completion and a completion resolve request.
|
||||||
// data ? : any
|
// data ? : any
|
||||||
|
|
||||||
inline bool operator==(const lsCompletionItem& other) const {
|
|
||||||
if (textEdit && other.textEdit)
|
|
||||||
return textEdit->newText == other.textEdit->newText;
|
|
||||||
if (!insertText.empty())
|
|
||||||
return insertText == other.insertText;
|
|
||||||
return label == other.label;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
MAKE_REFLECT_STRUCT(lsCompletionItem,
|
MAKE_REFLECT_STRUCT(lsCompletionItem,
|
||||||
label,
|
label,
|
||||||
@ -478,17 +470,6 @@ MAKE_REFLECT_STRUCT(lsCompletionItem,
|
|||||||
insertText,
|
insertText,
|
||||||
insertTextFormat,
|
insertTextFormat,
|
||||||
textEdit);
|
textEdit);
|
||||||
namespace std {
|
|
||||||
template<> struct hash<lsCompletionItem> {
|
|
||||||
std::size_t operator()(const lsCompletionItem& t) const {
|
|
||||||
if (t.textEdit)
|
|
||||||
return std::hash<std::string>()(t.textEdit->newText);
|
|
||||||
if (!t.insertText.empty())
|
|
||||||
return std::hash<std::string>()(t.insertText);
|
|
||||||
return std::hash<std::string>()(t.label);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct lsTextDocumentItem {
|
struct lsTextDocumentItem {
|
||||||
// The text document's URI.
|
// The text document's URI.
|
||||||
|
@ -41,5 +41,7 @@ int64_t GetLastModificationTime(const std::string& absolute_path);
|
|||||||
|
|
||||||
void CopyFileTo(const std::string& destination, const std::string& source);
|
void CopyFileTo(const std::string& destination, const std::string& source);
|
||||||
|
|
||||||
|
bool IsSymLink(const std::string& path);
|
||||||
|
|
||||||
// Returns any clang arguments that are specific to the current platform.
|
// Returns any clang arguments that are specific to the current platform.
|
||||||
std::vector<std::string> GetPlatformClangArguments();
|
std::vector<std::string> GetPlatformClangArguments();
|
@ -227,6 +227,13 @@ out_error:
|
|||||||
close(fd_to);
|
close(fd_to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsSymLink(const std::string& path) {
|
||||||
|
struct stat buf;
|
||||||
|
int result;
|
||||||
|
result = lstat(path.c_str(), &buf);
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::string> GetPlatformClangArguments() {
|
std::vector<std::string> GetPlatformClangArguments() {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -207,6 +207,10 @@ void CopyFileTo(const std::string& destination, const std::string& source) {
|
|||||||
false /*failIfExists*/);
|
false /*failIfExists*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsSymLink(const std::string& path) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::string> GetPlatformClangArguments() {
|
std::vector<std::string> GetPlatformClangArguments() {
|
||||||
return {
|
return {
|
||||||
"-fms-compatibility",
|
"-fms-compatibility",
|
||||||
|
15
src/utils.cc
15
src/utils.cc
@ -1,5 +1,9 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
|
#include <tinydir.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@ -10,8 +14,6 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include <tinydir.h>
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// See http://stackoverflow.com/a/217605
|
// See http://stackoverflow.com/a/217605
|
||||||
@ -120,12 +122,15 @@ static void GetFilesInFolderHelper(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Skip all dot files.
|
// Skip all dot files.
|
||||||
|
// We must always ignore the '.' and '..' directories, otherwise
|
||||||
|
// this will loop infinitely.
|
||||||
|
// The nested ifs are intentional, branching order is subtle here.
|
||||||
if (file.name[0] != '.') {
|
if (file.name[0] != '.') {
|
||||||
if (file.is_dir) {
|
if (file.is_dir) {
|
||||||
if (recursive) {
|
if (recursive) {
|
||||||
// Note that we must always ignore the '.' and '..' directories, otherwise
|
std::string child_dir = output_prefix + file.name + "/";
|
||||||
// this will loop infinitely. The above check handles that for us.
|
if (!IsSymLink(child_dir))
|
||||||
GetFilesInFolderHelper(file.path, true /*recursive*/, output_prefix + file.name + "/", handler);
|
GetFilesInFolderHelper(file.path, true /*recursive*/, child_dir, handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
Loading…
Reference in New Issue
Block a user