PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/cpp/session/modules/clang/CodeCompletion.cpp

http://github.com/rstudio/rstudio
C++ | 588 lines | 439 code | 87 blank | 62 comment | 62 complexity | b6d1aac7cf98e218eb9b56e0a2edefc5 MD5 | raw file
Possible License(s): AGPL-3.0, MIT, MPL-2.0-no-copyleft-exception, BSD-3-Clause, Apache-2.0
  1. /*
  2. * CodeCompletion.cpp
  3. *
  4. * Copyright (C) 2021 by RStudio, PBC
  5. *
  6. * Unless you have received this program directly from RStudio pursuant
  7. * to the terms of a commercial license agreement with RStudio, then
  8. * this program is licensed to you under the terms of version 3 of the
  9. * GNU Affero General Public License. This program is distributed WITHOUT
  10. * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
  11. * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
  12. * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
  13. *
  14. */
  15. #include "CodeCompletion.hpp"
  16. #include <iostream>
  17. #include <core/Debug.hpp>
  18. #include <shared_core/Error.hpp>
  19. #include <core/FileSerializer.hpp>
  20. #include <core/system/Process.hpp>
  21. #include <core/RegexUtils.hpp>
  22. #include <r/RExec.hpp>
  23. #include <session/projects/SessionProjects.hpp>
  24. #include <session/SessionModuleContext.hpp>
  25. #include "RCompilationDatabase.hpp"
  26. #include "RSourceIndex.hpp"
  27. #include "Diagnostics.hpp"
  28. using namespace rstudio::core;
  29. using namespace rstudio::core::libclang;
  30. #ifndef _WIN32
  31. # define kDevNull "/dev/null"
  32. #else
  33. # define kDevNull "NUL"
  34. #endif
  35. namespace rstudio {
  36. namespace session {
  37. namespace modules {
  38. namespace clang {
  39. namespace {
  40. // cache our system include paths
  41. std::vector<std::string> s_systemIncludePaths;
  42. json::Object friendlyCompletionText(const CodeCompleteResult& result)
  43. {
  44. // transform text
  45. std::string text = result.getText();
  46. boost::algorithm::replace_all(
  47. text,
  48. "std::basic_string<char, std::char_traits<char>, std::allocator<char> >",
  49. "std::string");
  50. // creat text object
  51. json::Object textJson;
  52. textJson["text"] = text;
  53. textJson["comment"] = result.getComment();
  54. return textJson;
  55. }
  56. const int kCompletionUnknown = 0;
  57. const int kCompletionVariable = 1;
  58. const int kCompletionFunction = 2;
  59. const int kCompletionConstructor = 3;
  60. const int kCompletionDestructor = 4;
  61. const int kCompletionClass = 5;
  62. const int kCompletionStruct = 6;
  63. const int kCompletionNamespace = 7;
  64. const int kCompletionEnum = 8;
  65. const int kCompletionEnumValue = 9;
  66. const int kCompletionKeyword = 10;
  67. const int kCompletionMacro = 11;
  68. const int kCompletionFile = 12;
  69. const int kCompletionDirectory = 13;
  70. int completionType(CXCursorKind kind)
  71. {
  72. switch(kind)
  73. {
  74. case CXCursor_UnexposedDecl:
  75. return kCompletionVariable;
  76. case CXCursor_StructDecl:
  77. case CXCursor_UnionDecl:
  78. return kCompletionStruct;
  79. case CXCursor_ClassDecl:
  80. return kCompletionClass;
  81. case CXCursor_EnumDecl:
  82. return kCompletionEnum;
  83. case CXCursor_FieldDecl:
  84. return kCompletionVariable;
  85. case CXCursor_EnumConstantDecl:
  86. return kCompletionEnumValue;
  87. case CXCursor_FunctionDecl:
  88. return kCompletionFunction;
  89. case CXCursor_VarDecl:
  90. case CXCursor_ParmDecl:
  91. return kCompletionVariable;
  92. case CXCursor_ObjCInterfaceDecl:
  93. case CXCursor_ObjCCategoryDecl:
  94. case CXCursor_ObjCProtocolDecl:
  95. return kCompletionClass;
  96. case CXCursor_ObjCPropertyDecl:
  97. case CXCursor_ObjCIvarDecl:
  98. return kCompletionVariable;
  99. case CXCursor_ObjCInstanceMethodDecl:
  100. case CXCursor_ObjCClassMethodDecl:
  101. return kCompletionFunction;
  102. case CXCursor_ObjCImplementationDecl:
  103. case CXCursor_ObjCCategoryImplDecl:
  104. return kCompletionClass;
  105. case CXCursor_TypedefDecl: // while these are typically classes, we don't
  106. // have access to the underlying cursor for the
  107. // completion (just a CXCursorKind) so there is
  108. // no way to know for sure
  109. return kCompletionClass;
  110. case CXCursor_CXXMethod:
  111. return kCompletionFunction;
  112. case CXCursor_Namespace:
  113. return kCompletionNamespace;
  114. case CXCursor_LinkageSpec:
  115. return kCompletionKeyword;
  116. case CXCursor_Constructor:
  117. return kCompletionConstructor;
  118. case CXCursor_Destructor:
  119. return kCompletionDestructor;
  120. case CXCursor_ConversionFunction:
  121. return kCompletionFunction;
  122. case CXCursor_TemplateTypeParameter:
  123. case CXCursor_NonTypeTemplateParameter:
  124. return kCompletionVariable;
  125. case CXCursor_FunctionTemplate:
  126. return kCompletionFunction;
  127. case CXCursor_ClassTemplate:
  128. case CXCursor_ClassTemplatePartialSpecialization:
  129. return kCompletionClass;
  130. case CXCursor_NamespaceAlias:
  131. case CXCursor_UsingDirective:
  132. case CXCursor_UsingDeclaration:
  133. case CXCursor_TypeAliasDecl:
  134. return kCompletionVariable;
  135. case CXCursor_ObjCSynthesizeDecl:
  136. case CXCursor_ObjCDynamicDecl:
  137. case CXCursor_CXXAccessSpecifier:
  138. return kCompletionKeyword;
  139. case CXCursor_MacroDefinition:
  140. return kCompletionMacro;
  141. default:
  142. return kCompletionUnknown;
  143. }
  144. }
  145. core::json::Object toJson(const CodeCompleteResult& result)
  146. {
  147. json::Object resultJson;
  148. resultJson["type"] = completionType(result.getKind());
  149. resultJson["typed_text"] = result.getTypedText();
  150. json::Array textJson;
  151. textJson.push_back(friendlyCompletionText(result));
  152. resultJson["text"] = textJson;
  153. return resultJson;
  154. }
  155. void discoverTranslationUnitIncludePaths(const FilePath& filePath,
  156. std::vector<std::string>* pIncludePaths)
  157. {
  158. std::vector<std::string> args =
  159. rCompilationDatabase().compileArgsForTranslationUnit(
  160. filePath.getAbsolutePathNative(), false);
  161. for (const std::string& arg : args)
  162. {
  163. if (boost::algorithm::starts_with(arg, "-I"))
  164. {
  165. // skip RStudio's libclang builtin header paths
  166. if (arg.find("libclang/builtin-headers") != std::string::npos)
  167. continue;
  168. pIncludePaths->push_back(
  169. string_utils::strippedOfQuotes(arg.substr(2)));
  170. }
  171. }
  172. }
  173. } // end anonymous namespace
  174. void discoverSystemIncludePaths(std::vector<std::string>* pIncludePaths)
  175. {
  176. // if we've cached results already, just use that
  177. if (!s_systemIncludePaths.empty())
  178. {
  179. pIncludePaths->insert(
  180. pIncludePaths->end(),
  181. s_systemIncludePaths.begin(),
  182. s_systemIncludePaths.end());
  183. return;
  184. }
  185. core::system::ProcessOptions processOptions;
  186. processOptions.redirectStdErrToStdOut = true;
  187. // add Rtools to PATH if necessary
  188. core::system::Options environment;
  189. core::system::environment(&environment);
  190. std::string warning;
  191. module_context::addRtoolsToPathIfNecessary(&environment, &warning);
  192. processOptions.environment = environment;
  193. // get the CXX compiler by asking R
  194. std::string compilerPath;
  195. #ifdef _WIN32
  196. {
  197. core::system::ProcessResult result;
  198. Error error = core::system::runCommand("where.exe gcc.exe", processOptions, &result);
  199. if (error)
  200. LOG_ERROR(error);
  201. else if (result.exitStatus != EXIT_SUCCESS)
  202. LOG_ERROR_MESSAGE("Error querying CXX compiler: " + result.stdOut);
  203. else
  204. compilerPath = string_utils::trimWhitespace(result.stdOut);
  205. }
  206. #else
  207. {
  208. Error error;
  209. // resolve R CMD location for shell command
  210. FilePath rBinDir;
  211. error = module_context::rBinDir(&rBinDir);
  212. if (error)
  213. {
  214. LOG_ERROR(error);
  215. return;
  216. }
  217. shell_utils::ShellCommand rCmd = module_context::rCmd(rBinDir);
  218. core::system::ProcessResult result;
  219. rCmd << "config";
  220. rCmd << "CXX";
  221. error = core::system::runCommand(rCmd, processOptions, &result);
  222. if (error)
  223. LOG_ERROR(error);
  224. else if (result.exitStatus != EXIT_SUCCESS)
  225. LOG_ERROR_MESSAGE("Error querying CXX compiler: " + result.stdOut);
  226. else
  227. compilerPath = string_utils::trimWhitespace(result.stdOut);
  228. }
  229. #endif
  230. if (compilerPath.empty())
  231. return;
  232. // it is likely that R is configured to use a compiler that exists
  233. // on the PATH; however, when invoked through 'runCommand()' this
  234. // can fail unless we explicitly find and resolve that path. also
  235. // note that one can configure CXX with multiple arguments
  236. // (e.g. as 'g++ -std=c++17') so we must also tear off only the
  237. // compiler name. we hence assume that the supplemental arguments
  238. // to CXX do not influence the compiler include paths.
  239. #ifndef _WIN32
  240. {
  241. std::string compilerName = compilerPath;
  242. std::size_t index = compilerPath.find(' ');
  243. if (index != std::string::npos)
  244. {
  245. compilerName = compilerPath.substr(0, index);
  246. }
  247. else
  248. {
  249. compilerName = compilerPath;
  250. }
  251. core::system::ProcessResult result;
  252. std::vector<std::string> args = { compilerName };
  253. Error error = core::system::runProgram("/usr/bin/which", args, "", processOptions, &result);
  254. if (error)
  255. LOG_ERROR(error);
  256. else if (result.exitStatus != EXIT_SUCCESS)
  257. LOG_ERROR_MESSAGE("Error qualifying CXX compiler path: " + result.stdOut);
  258. else
  259. compilerPath = string_utils::trimWhitespace(result.stdOut);
  260. }
  261. #endif
  262. // ask the compiler what the system include paths are (note that both
  263. // gcc and clang accept the same command)
  264. std::string includePathOutput;
  265. {
  266. core::system::ProcessResult result;
  267. std::string cmd = compilerPath + " -E -x c++ - -v < " kDevNull;
  268. Error error = core::system::runCommand(cmd, processOptions, &result);
  269. if (error)
  270. LOG_ERROR(error);
  271. else if (result.exitStatus != EXIT_SUCCESS)
  272. LOG_ERROR_MESSAGE("Error retrieving system include paths: " + result.stdOut);
  273. else
  274. includePathOutput = string_utils::trimWhitespace(result.stdOut);
  275. }
  276. if (includePathOutput.empty())
  277. return;
  278. // strip out the include paths from the output
  279. std::string startString = "#include <...> search starts here:";
  280. std::string endString = "End of search list.";
  281. std::string::size_type startPos = includePathOutput.find(startString);
  282. if (startPos == std::string::npos)
  283. return;
  284. std::string::size_type endPos = includePathOutput.find(endString, startPos);
  285. if (endPos == std::string::npos)
  286. return;
  287. std::string includePathsString = string_utils::trimWhitespace(
  288. string_utils::substring(includePathOutput,
  289. startPos + startString.size(),
  290. endPos));
  291. // split on newlines
  292. std::vector<std::string> includePaths = core::algorithm::split(includePathsString, "\n");
  293. // remove framework directories
  294. core::algorithm::expel_if(includePaths, string_utils::Contains("(framework directory)"));
  295. // trim whitespace
  296. for (std::size_t i = 0, n = includePaths.size(); i < n; ++i)
  297. includePaths[i] = string_utils::trimWhitespace(includePaths[i]);
  298. // update cache
  299. s_systemIncludePaths = includePaths;
  300. // fill result vector
  301. pIncludePaths->insert(
  302. pIncludePaths->end(),
  303. includePaths.begin(),
  304. includePaths.end());
  305. }
  306. namespace {
  307. void discoverRelativeIncludePaths(const FilePath& filePath,
  308. const std::string& parentDir,
  309. std::vector<std::string>* pIncludePaths)
  310. {
  311. // Construct the directory in which to search for includes
  312. FilePath targetPath = filePath.getParent().completePath(parentDir);
  313. if (!targetPath.exists())
  314. return;
  315. pIncludePaths->push_back(targetPath.getAbsolutePath());
  316. }
  317. json::Object jsonHeaderCompletionResult(const std::string& name,
  318. const std::string& source,
  319. int completionType)
  320. {
  321. json::Object completionJson;
  322. completionJson["type"] = completionType;
  323. completionJson["typed_text"] = name;
  324. json::Array textJson;
  325. json::Object objectJson;
  326. objectJson["text"] = name;
  327. objectJson["comment"] = source;
  328. textJson.push_back(objectJson);
  329. completionJson["text"] = textJson;
  330. return completionJson;
  331. }
  332. Error getHeaderCompletionsImpl(const std::string& token,
  333. const std::string& parentDir,
  334. const FilePath& filePath,
  335. const std::string& docId,
  336. bool systemHeadersOnly,
  337. const core::json::JsonRpcRequest& request,
  338. core::json::JsonRpcResponse* pResponse)
  339. {
  340. std::vector<std::string> includePaths;
  341. // discover the system headers
  342. discoverSystemIncludePaths(&includePaths);
  343. // discover TU-related include paths
  344. discoverTranslationUnitIncludePaths(filePath, &includePaths);
  345. // discover local include paths
  346. if (!systemHeadersOnly)
  347. discoverRelativeIncludePaths(filePath, parentDir, &includePaths);
  348. // remove dupes
  349. std::sort(includePaths.begin(), includePaths.end());
  350. includePaths.erase(std::unique(includePaths.begin(), includePaths.end()),
  351. includePaths.end());
  352. // loop through header include paths and return paths that match token
  353. std::set<std::string> discoveredEntries;
  354. json::Array completionsJson;
  355. for (const std::string& path : includePaths)
  356. {
  357. FilePath includePath(path);
  358. if (!includePath.exists())
  359. continue;
  360. FilePath targetPath = includePath.completePath(parentDir);
  361. if (!targetPath.exists())
  362. continue;
  363. std::vector<FilePath> children;
  364. Error error = targetPath.getChildren(children);
  365. if (error)
  366. LOG_ERROR(error);
  367. for (const FilePath& childPath : children)
  368. {
  369. std::string name = childPath.getFilename();
  370. if (discoveredEntries.count(name))
  371. continue;
  372. std::string extension = childPath.getExtensionLowerCase();
  373. if (!(extension == ".h" || extension == ".hpp" || extension == ""))
  374. continue;
  375. if (string_utils::isSubsequence(name, token, true))
  376. {
  377. int type = childPath.isDirectory() ? kCompletionDirectory : kCompletionFile;
  378. completionsJson.push_back(jsonHeaderCompletionResult(name,
  379. childPath.getAbsolutePath(),
  380. type));
  381. }
  382. discoveredEntries.insert(name);
  383. }
  384. }
  385. // construct completion result
  386. json::Object resultJson;
  387. resultJson["completions"] = completionsJson;
  388. pResponse->setResult(resultJson);
  389. return Success();
  390. }
  391. Error getHeaderCompletions(std::string line,
  392. const FilePath& filePath,
  393. const std::string& docId,
  394. const core::json::JsonRpcRequest& request,
  395. core::json::JsonRpcResponse* pResponse)
  396. {
  397. // extract portion of the path that the user has produced
  398. std::string::size_type idx = line.find_first_of("<\"");
  399. if (idx == std::string::npos)
  400. return Success();
  401. bool isSystemHeader = line[idx] == '<';
  402. std::string pathString = line.substr(idx + 1);
  403. // split path into 'parentDir' + 'token', e.g.
  404. //
  405. // #include <foo/bar/ba
  406. // ^^^^^^^|^^
  407. std::string parentDir, token;
  408. std::string::size_type pathDelimIdx = pathString.find_last_of("/");
  409. if (pathDelimIdx == std::string::npos)
  410. {
  411. token = pathString;
  412. }
  413. else
  414. {
  415. parentDir = pathString.substr(0, pathDelimIdx);
  416. token = pathString.substr(pathDelimIdx + 1);
  417. }
  418. return getHeaderCompletionsImpl(token,
  419. parentDir,
  420. filePath,
  421. docId,
  422. isSystemHeader,
  423. request,
  424. pResponse);
  425. }
  426. } // anonymous namespace
  427. Error getCppCompletions(const core::json::JsonRpcRequest& request,
  428. core::json::JsonRpcResponse* pResponse)
  429. {
  430. // empty response by default
  431. pResponse->setResult(json::Value());
  432. // get params
  433. std::string line, docPath, docId, userText;
  434. int row, column;
  435. Error error = json::readParams(request.params,
  436. &line,
  437. &docPath,
  438. &docId,
  439. &row,
  440. &column,
  441. &userText);
  442. if (error)
  443. return error;
  444. // resolve the docPath if it's aliased
  445. FilePath filePath = module_context::resolveAliasedPath(docPath);
  446. // if it looks like we're requesting autocompletions for an '#include'
  447. // statement, take a separate completion path
  448. static const boost::regex reInclude("^\\s*#+\\s*include");
  449. if (regex_utils::textMatches(line, reInclude, true, true))
  450. return getHeaderCompletions(line, filePath, docId, request, pResponse);
  451. // get the translation unit and do the code completion
  452. std::string filename = filePath.getAbsolutePath();
  453. TranslationUnit tu = rSourceIndex().getTranslationUnit(filename);
  454. if (!tu.empty())
  455. {
  456. std::string lastTypedText;
  457. json::Array completionsJson;
  458. boost::shared_ptr<CodeCompleteResults> pResults =
  459. tu.codeCompleteAt(filename, row, column);
  460. if (!pResults->empty())
  461. {
  462. // get results
  463. for (unsigned i = 0; i<pResults->getNumResults(); i++)
  464. {
  465. CodeCompleteResult result = pResults->getResult(i);
  466. // filter on user text if we have it
  467. if (!userText.empty() &&
  468. !boost::algorithm::starts_with(result.getTypedText(), userText))
  469. {
  470. continue;
  471. }
  472. // check whether this completion is valid and bail if not
  473. if (result.getAvailability() != CXAvailability_Available)
  474. {
  475. continue;
  476. }
  477. std::string typedText = result.getTypedText();
  478. // if we have the same typed text then just ammend previous result
  479. if ((typedText == lastTypedText) && !completionsJson.isEmpty())
  480. {
  481. json::Object res = completionsJson.getBack().getObject();
  482. json::Array text = res["text"].getArray();
  483. text.push_back(friendlyCompletionText(result));
  484. }
  485. else
  486. {
  487. completionsJson.push_back(toJson(result));
  488. }
  489. lastTypedText = typedText;
  490. }
  491. }
  492. json::Object resultJson;
  493. resultJson["completions"] = completionsJson.clone();
  494. pResponse->setResult(resultJson);
  495. }
  496. return Success();
  497. }
  498. } // namespace clang
  499. } // namespace modules
  500. } // namespace session
  501. } // namespace rstudio