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 &param) {
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 &param) {
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 &param,
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