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