1 // Copyright 2017-2018 ccls Authors
2 // SPDX-License-Identifier: Apache-2.0
3
4 #include "fuzzy_match.hh"
5 #include "log.hh"
6 #include "message_handler.hh"
7 #include "pipeline.hh"
8 #include "project.hh"
9 #include "query.hh"
10 #include "sema_manager.hh"
11
12 #include <llvm/ADT/STLExtras.h>
13 #include <llvm/ADT/StringRef.h>
14 #include <llvm/Support/Path.h>
15
16 #include <algorithm>
17 #include <ctype.h>
18 #include <functional>
19 #include <limits.h>
20 using namespace llvm;
21
22 namespace ccls {
23 REFLECT_STRUCT(SymbolInformation, name, kind, location, containerName);
24
workspace_didChangeConfiguration(EmptyParam &)25 void MessageHandler::workspace_didChangeConfiguration(EmptyParam &) {
26 for (auto &[folder, _] : g_config->workspaceFolders)
27 project->load(folder);
28 project->index(wfiles, RequestId());
29
30 manager->clear();
31 };
32
workspace_didChangeWatchedFiles(DidChangeWatchedFilesParam & param)33 void MessageHandler::workspace_didChangeWatchedFiles(
34 DidChangeWatchedFilesParam ¶m) {
35 for (auto &event : param.changes) {
36 std::string path = event.uri.getPath();
37 if ((g_config->cache.directory.size() &&
38 StringRef(path).startswith(g_config->cache.directory)) ||
39 lookupExtension(path).first == LanguageId::Unknown)
40 return;
41 for (std::string cur = path; cur.size(); cur = sys::path::parent_path(cur))
42 if (cur[0] == '.')
43 return;
44
45 switch (event.type) {
46 case FileChangeType::Created:
47 case FileChangeType::Changed: {
48 IndexMode mode =
49 wfiles->getFile(path) ? IndexMode::Normal : IndexMode::Background;
50 pipeline::index(path, {}, mode, true);
51 if (event.type == FileChangeType::Changed) {
52 if (mode == IndexMode::Normal)
53 manager->onSave(path);
54 else
55 manager->onClose(path);
56 }
57 break;
58 }
59 case FileChangeType::Deleted:
60 pipeline::index(path, {}, IndexMode::Delete, false);
61 manager->onClose(path);
62 break;
63 }
64 }
65 }
66
workspace_didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParam & param)67 void MessageHandler::workspace_didChangeWorkspaceFolders(
68 DidChangeWorkspaceFoldersParam ¶m) {
69 for (const WorkspaceFolder &wf : param.event.removed) {
70 std::string root = wf.uri.getPath();
71 ensureEndsInSlash(root);
72 LOG_S(INFO) << "delete workspace folder " << wf.name << ": " << root;
73 auto it = llvm::find_if(g_config->workspaceFolders,
74 [&](auto &folder) { return folder.first == root; });
75 if (it != g_config->workspaceFolders.end()) {
76 g_config->workspaceFolders.erase(it);
77 {
78 // auto &folder = project->root2folder[path];
79 // FIXME delete
80 }
81 project->root2folder.erase(root);
82 }
83 }
84 auto &workspaceFolders = g_config->workspaceFolders;
85 for (const WorkspaceFolder &wf : param.event.added) {
86 std::string folder = wf.uri.getPath();
87 ensureEndsInSlash(folder);
88 std::string real = realPath(folder) + '/';
89 if (folder == real)
90 real.clear();
91 LOG_S(INFO) << "add workspace folder " << wf.name << ": "
92 << (real.empty() ? folder : (folder + " -> ").append(real));
93 workspaceFolders.emplace_back();
94 auto it = workspaceFolders.end() - 1;
95 for (; it != workspaceFolders.begin() && folder < it[-1].first; --it)
96 *it = it[-1];
97 *it = {folder, real};
98 project->load(folder);
99 }
100
101 project->index(wfiles, RequestId());
102
103 manager->clear();
104 }
105
106 namespace {
107 // Lookup |symbol| in |db| and insert the value into |result|.
addSymbol(DB * db,WorkingFiles * wfiles,const std::vector<uint8_t> & file_set,SymbolIdx sym,bool use_detailed,std::vector<std::tuple<SymbolInformation,int,SymbolIdx>> * result)108 bool addSymbol(
109 DB *db, WorkingFiles *wfiles, const std::vector<uint8_t> &file_set,
110 SymbolIdx sym, bool use_detailed,
111 std::vector<std::tuple<SymbolInformation, int, SymbolIdx>> *result) {
112 std::optional<SymbolInformation> info = getSymbolInfo(db, sym, true);
113 if (!info)
114 return false;
115
116 Maybe<DeclRef> dr;
117 bool in_folder = false;
118 withEntity(db, sym, [&](const auto &entity) {
119 for (auto &def : entity.def)
120 if (def.spell) {
121 dr = def.spell;
122 if (!in_folder && (in_folder = file_set[def.spell->file_id]))
123 break;
124 }
125 });
126 if (!dr) {
127 auto &decls = getNonDefDeclarations(db, sym);
128 for (auto &dr1 : decls) {
129 dr = dr1;
130 if (!in_folder && (in_folder = file_set[dr1.file_id]))
131 break;
132 }
133 }
134 if (!in_folder)
135 return false;
136
137 std::optional<Location> ls_location = getLsLocation(db, wfiles, *dr);
138 if (!ls_location)
139 return false;
140 info->location = *ls_location;
141 result->emplace_back(*info, int(use_detailed), sym);
142 return true;
143 }
144 } // namespace
145
workspace_symbol(WorkspaceSymbolParam & param,ReplyOnce & reply)146 void MessageHandler::workspace_symbol(WorkspaceSymbolParam ¶m,
147 ReplyOnce &reply) {
148 std::vector<SymbolInformation> result;
149 const std::string &query = param.query;
150 for (auto &folder : param.folders)
151 ensureEndsInSlash(folder);
152 std::vector<uint8_t> file_set = db->getFileSet(param.folders);
153
154 // {symbol info, matching detailed_name or short_name, index}
155 std::vector<std::tuple<SymbolInformation, int, SymbolIdx>> cands;
156 bool sensitive = g_config->workspaceSymbol.caseSensitivity;
157
158 // Find subsequence matches.
159 std::string query_without_space;
160 query_without_space.reserve(query.size());
161 for (char c : query)
162 if (!isspace(c))
163 query_without_space += c;
164
165 auto add = [&](SymbolIdx sym) {
166 std::string_view detailed_name = db->getSymbolName(sym, true);
167 int pos = reverseSubseqMatch(query_without_space, detailed_name, sensitive);
168 return pos >= 0 &&
169 addSymbol(db, wfiles, file_set, sym,
170 detailed_name.find(':', pos) != std::string::npos,
171 &cands) &&
172 cands.size() >= g_config->workspaceSymbol.maxNum;
173 };
174 for (auto &func : db->funcs)
175 if (add({func.usr, Kind::Func}))
176 goto done_add;
177 for (auto &type : db->types)
178 if (add({type.usr, Kind::Type}))
179 goto done_add;
180 for (auto &var : db->vars)
181 if (var.def.size() && !var.def[0].is_local() && add({var.usr, Kind::Var}))
182 goto done_add;
183 done_add:
184
185 if (g_config->workspaceSymbol.sort && query.size() <= FuzzyMatcher::kMaxPat) {
186 // Sort results with a fuzzy matching algorithm.
187 int longest = 0;
188 for (auto &cand : cands)
189 longest = std::max(
190 longest, int(db->getSymbolName(std::get<2>(cand), true).size()));
191 FuzzyMatcher fuzzy(query, g_config->workspaceSymbol.caseSensitivity);
192 for (auto &cand : cands)
193 std::get<1>(cand) = fuzzy.match(
194 db->getSymbolName(std::get<2>(cand), std::get<1>(cand)), false);
195 std::sort(cands.begin(), cands.end(), [](const auto &l, const auto &r) {
196 return std::get<1>(l) > std::get<1>(r);
197 });
198 result.reserve(cands.size());
199 for (auto &cand : cands) {
200 // Discard awful candidates.
201 if (std::get<1>(cand) <= FuzzyMatcher::kMinScore)
202 break;
203 result.push_back(std::get<0>(cand));
204 }
205 } else {
206 result.reserve(cands.size());
207 for (auto &cand : cands)
208 result.push_back(std::get<0>(cand));
209 }
210
211 reply(result);
212 }
213 } // namespace ccls
214