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:
Jacob Dufault 2017-05-23 00:24:14 -07:00
parent ee90938b28
commit 37787290cb
7 changed files with 66 additions and 45 deletions

View File

@ -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*/));
} }
} }

View File

@ -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_;

View File

@ -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.

View File

@ -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();

View File

@ -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 {};
} }

View File

@ -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",

View File

@ -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 {