1 #include "include_complete.h"
2 
3 #include "match.h"
4 #include "platform.h"
5 #include "project.h"
6 #include "standard_includes.h"
7 #include "timer.h"
8 #include "work_thread.h"
9 
10 #include <thread>
11 
12 namespace {
13 
14 struct CompletionCandidate {
15   std::string absolute_path;
16   lsCompletionItem completion_item;
17 };
18 
ElideLongPath(const std::string & path)19 std::string ElideLongPath(const std::string& path) {
20   if (g_config->completion.includeMaxPathSize <= 0)
21     return path;
22 
23   if ((int)path.size() <= g_config->completion.includeMaxPathSize)
24     return path;
25 
26   size_t start = path.size() - g_config->completion.includeMaxPathSize;
27   return ".." + path.substr(start + 2);
28 }
29 
TrimCommonPathPrefix(const std::string & result,const std::string & trimmer)30 size_t TrimCommonPathPrefix(const std::string& result,
31                             const std::string& trimmer) {
32   size_t i = 0;
33   while (i < result.size() && i < trimmer.size()) {
34     char a = result[i];
35     char b = trimmer[i];
36 #if defined(_WIN32)
37     a = (char)tolower(a);
38     b = (char)tolower(b);
39 #endif
40     if (a != b)
41       break;
42     ++i;
43   }
44 
45   if (i == trimmer.size())
46     return i;
47   return 0;
48 }
49 
50 // Returns true iff angle brackets should be used.
TrimPath(Project * project,const std::string & project_root,std::string * insert_path)51 bool TrimPath(Project* project,
52               const std::string& project_root,
53               std::string* insert_path) {
54   size_t start = 0;
55   bool angle = false;
56 
57   size_t len = TrimCommonPathPrefix(*insert_path, project_root);
58   if (len > start)
59     start = len;
60 
61   for (const Directory& include_dir : project->quote_include_directories) {
62     len = TrimCommonPathPrefix(*insert_path, include_dir.path);
63     if (len > start)
64       start = len;
65   }
66 
67   for (const Directory& include_dir : project->angle_include_directories) {
68     len = TrimCommonPathPrefix(*insert_path, include_dir.path);
69     if (len > start) {
70       start = len;
71       angle = true;
72     }
73   }
74 
75   *insert_path = insert_path->substr(start);
76   return angle;
77 }
78 
BuildCompletionItem(const std::string & path,bool use_angle_brackets,bool is_stl)79 lsCompletionItem BuildCompletionItem(const std::string& path,
80                                      bool use_angle_brackets,
81                                      bool is_stl) {
82   lsCompletionItem item;
83   item.label = ElideLongPath(path);
84   item.detail = path;  // the include path, used in de-duplicating
85   item.textEdit = lsTextEdit();
86   item.textEdit->newText = path;
87   item.insertTextFormat = lsInsertTextFormat::PlainText;
88   item.use_angle_brackets_ = use_angle_brackets;
89   if (is_stl) {
90     item.kind = lsCompletionItemKind::Module;
91     item.priority_ = 2;
92   } else {
93     item.kind = lsCompletionItemKind::File;
94     item.priority_ = 1;
95   }
96   return item;
97 }
98 
99 }  // namespace
100 
IncludeComplete(Project * project)101 IncludeComplete::IncludeComplete(Project* project)
102     : is_scanning(false), project_(project) {}
103 
Rescan()104 void IncludeComplete::Rescan() {
105   if (is_scanning)
106     return;
107 
108   completion_items.clear();
109   absolute_path_to_completion_item.clear();
110   inserted_paths.clear();
111 
112   if (!match_ && (!g_config->completion.includeWhitelist.empty() ||
113                   !g_config->completion.includeBlacklist.empty()))
114     match_ =
115         std::make_unique<GroupMatch>(g_config->completion.includeWhitelist,
116                                      g_config->completion.includeBlacklist);
117 
118   is_scanning = true;
119   WorkThread::StartThread("scan_includes", [this]() {
120     Timer timer;
121 
122     InsertStlIncludes();
123     InsertIncludesFromDirectory(g_config->projectRoot,
124                                 false /*use_angle_brackets*/);
125     for (const Directory& dir : project_->quote_include_directories)
126       InsertIncludesFromDirectory(dir.path, false /*use_angle_brackets*/);
127     for (const Directory& dir : project_->angle_include_directories)
128       InsertIncludesFromDirectory(dir.path, true /*use_angle_brackets*/);
129 
130     timer.ResetAndPrint("[perf] Scanning for includes");
131     is_scanning = false;
132   });
133 }
134 
InsertCompletionItem(const std::string & absolute_path,lsCompletionItem && item)135 void IncludeComplete::InsertCompletionItem(const std::string& absolute_path,
136                                            lsCompletionItem&& item) {
137   if (inserted_paths.insert({item.detail, inserted_paths.size()}).second) {
138     completion_items.push_back(item);
139     // insert if not found or with shorter include path
140     auto it = absolute_path_to_completion_item.find(absolute_path);
141     if (it == absolute_path_to_completion_item.end() ||
142         completion_items[it->second].detail.length() > item.detail.length()) {
143       absolute_path_to_completion_item[absolute_path] =
144           completion_items.size() - 1;
145     }
146   } else {
147     lsCompletionItem& inserted_item =
148         completion_items[inserted_paths[item.detail]];
149     // Update |use_angle_brackets_|, prefer quotes.
150     if (!item.use_angle_brackets_)
151       inserted_item.use_angle_brackets_ = false;
152   }
153 }
154 
AddFile(const std::string & absolute_path)155 void IncludeComplete::AddFile(const std::string& absolute_path) {
156   if (!EndsWithAny(absolute_path, g_config->completion.includeSuffixWhitelist))
157     return;
158   if (match_ && !match_->IsMatch(absolute_path))
159     return;
160 
161   std::string trimmed_path = absolute_path;
162   bool use_angle_brackets =
163       TrimPath(project_, g_config->projectRoot, &trimmed_path);
164   lsCompletionItem item =
165       BuildCompletionItem(trimmed_path, use_angle_brackets, false /*is_stl*/);
166 
167   std::unique_lock<std::mutex> lock(completion_items_mutex, std::defer_lock);
168   if (is_scanning)
169     lock.lock();
170   InsertCompletionItem(absolute_path, std::move(item));
171   if (lock)
172     lock.unlock();
173 }
174 
InsertIncludesFromDirectory(std::string directory0,bool use_angle_brackets)175 void IncludeComplete::InsertIncludesFromDirectory(std::string directory0,
176                                                   bool use_angle_brackets) {
177   optional<AbsolutePath> directory = NormalizePath(directory0);
178   if (!directory)
179     return;
180 
181   EnsureEndsInSlash(directory->path);
182   if (match_ && !match_->IsMatch(directory->path)) {
183     // Don't even enter the directory if it fails the patterns.
184     return;
185   }
186 
187   std::vector<CompletionCandidate> results;
188   GetFilesAndDirectoriesInFolder(
189       directory->path, true /*recursive*/, false /*add_folder_to_path*/,
190       [&](const std::string& path) {
191         if (!EndsWithAny(path, g_config->completion.includeSuffixWhitelist))
192           return;
193         if (match_ && !match_->IsMatch(directory->path + path))
194           return;
195 
196         CompletionCandidate candidate;
197         candidate.absolute_path = directory->path + path;
198         candidate.completion_item =
199             BuildCompletionItem(path, use_angle_brackets, false /*is_stl*/);
200         results.push_back(candidate);
201       });
202 
203   std::lock_guard<std::mutex> lock(completion_items_mutex);
204   for (CompletionCandidate& result : results)
205     InsertCompletionItem(result.absolute_path,
206                          std::move(result.completion_item));
207 }
208 
InsertStlIncludes()209 void IncludeComplete::InsertStlIncludes() {
210   std::lock_guard<std::mutex> lock(completion_items_mutex);
211   for (const char* stl_header : kStandardLibraryIncludes) {
212     completion_items.push_back(BuildCompletionItem(
213         stl_header, true /*use_angle_brackets*/, true /*is_stl*/));
214   }
215 }
216 
FindCompletionItemForAbsolutePath(const std::string & absolute_path)217 optional<lsCompletionItem> IncludeComplete::FindCompletionItemForAbsolutePath(
218     const std::string& absolute_path) {
219   std::lock_guard<std::mutex> lock(completion_items_mutex);
220 
221   auto it = absolute_path_to_completion_item.find(absolute_path);
222   if (it == absolute_path_to_completion_item.end())
223     return nullopt;
224   return completion_items[it->second];
225 }
226