1 //===--- CodeCompletionStrings.cpp -------------------------------*- C++-*-===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===---------------------------------------------------------------------===//
9 
10 #include "CodeCompletionStrings.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/AST/RawCommentList.h"
13 #include "clang/Basic/SourceManager.h"
14 #include <utility>
15 
16 namespace clang {
17 namespace clangd {
18 
19 namespace {
20 
21 bool isInformativeQualifierChunk(CodeCompletionString::Chunk const &Chunk) {
22   return Chunk.Kind == CodeCompletionString::CK_Informative &&
23          StringRef(Chunk.Text).endswith("::");
24 }
25 
26 void processPlainTextChunks(const CodeCompletionString &CCS,
27                             std::string *LabelOut, std::string *InsertTextOut) {
28   std::string &Label = *LabelOut;
29   std::string &InsertText = *InsertTextOut;
30   for (const auto &Chunk : CCS) {
31     // Informative qualifier chunks only clutter completion results, skip
32     // them.
33     if (isInformativeQualifierChunk(Chunk))
34       continue;
35 
36     switch (Chunk.Kind) {
37     case CodeCompletionString::CK_ResultType:
38     case CodeCompletionString::CK_Optional:
39       break;
40     case CodeCompletionString::CK_TypedText:
41       InsertText += Chunk.Text;
42       Label += Chunk.Text;
43       break;
44     default:
45       Label += Chunk.Text;
46       break;
47     }
48   }
49 }
50 
51 void appendEscapeSnippet(const llvm::StringRef Text, std::string *Out) {
52   for (const auto Character : Text) {
53     if (Character == '$' || Character == '}' || Character == '\\')
54       Out->push_back('\\');
55     Out->push_back(Character);
56   }
57 }
58 
59 void processSnippetChunks(const CodeCompletionString &CCS,
60                           std::string *LabelOut, std::string *InsertTextOut) {
61   std::string &Label = *LabelOut;
62   std::string &InsertText = *InsertTextOut;
63 
64   unsigned ArgCount = 0;
65   for (const auto &Chunk : CCS) {
66     // Informative qualifier chunks only clutter completion results, skip
67     // them.
68     if (isInformativeQualifierChunk(Chunk))
69       continue;
70 
71     switch (Chunk.Kind) {
72     case CodeCompletionString::CK_TypedText:
73     case CodeCompletionString::CK_Text:
74       Label += Chunk.Text;
75       InsertText += Chunk.Text;
76       break;
77     case CodeCompletionString::CK_Optional:
78       // FIXME: Maybe add an option to allow presenting the optional chunks?
79       break;
80     case CodeCompletionString::CK_Placeholder:
81       ++ArgCount;
82       InsertText += "${" + std::to_string(ArgCount) + ':';
83       appendEscapeSnippet(Chunk.Text, &InsertText);
84       InsertText += '}';
85       Label += Chunk.Text;
86       break;
87     case CodeCompletionString::CK_Informative:
88       // For example, the word "const" for a const method, or the name of
89       // the base class for methods that are part of the base class.
90       Label += Chunk.Text;
91       // Don't put the informative chunks in the insertText.
92       break;
93     case CodeCompletionString::CK_ResultType:
94       // This is retrieved as detail.
95       break;
96     case CodeCompletionString::CK_CurrentParameter:
97       // This should never be present while collecting completion items,
98       // only while collecting overload candidates.
99       llvm_unreachable("Unexpected CK_CurrentParameter while collecting "
100                        "CompletionItems");
101       break;
102     case CodeCompletionString::CK_LeftParen:
103     case CodeCompletionString::CK_RightParen:
104     case CodeCompletionString::CK_LeftBracket:
105     case CodeCompletionString::CK_RightBracket:
106     case CodeCompletionString::CK_LeftBrace:
107     case CodeCompletionString::CK_RightBrace:
108     case CodeCompletionString::CK_LeftAngle:
109     case CodeCompletionString::CK_RightAngle:
110     case CodeCompletionString::CK_Comma:
111     case CodeCompletionString::CK_Colon:
112     case CodeCompletionString::CK_SemiColon:
113     case CodeCompletionString::CK_Equal:
114     case CodeCompletionString::CK_HorizontalSpace:
115       InsertText += Chunk.Text;
116       Label += Chunk.Text;
117       break;
118     case CodeCompletionString::CK_VerticalSpace:
119       InsertText += Chunk.Text;
120       // Don't even add a space to the label.
121       break;
122     }
123   }
124 }
125 
126 std::string getFormattedComment(const ASTContext &Ctx, const RawComment &RC,
127                                 bool CommentsFromHeaders) {
128   auto &SourceMgr = Ctx.getSourceManager();
129   // Parsing comments from invalid preamble can lead to crashes. So we only
130   // return comments from the main file when doing code completion. For
131   // indexing, we still read all the comments.
132   // FIXME: find a better fix, e.g. store file contents in the preamble or get
133   // doc comments from the index.
134   if (!CommentsFromHeaders && !SourceMgr.isWrittenInMainFile(RC.getLocStart()))
135     return "";
136   return RC.getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
137 }
138 } // namespace
139 
140 std::string getDocComment(const ASTContext &Ctx,
141                           const CodeCompletionResult &Result,
142                           bool CommentsFromHeaders) {
143   // FIXME: clang's completion also returns documentation for RK_Pattern if they
144   // contain a pattern for ObjC properties. Unfortunately, there is no API to
145   // get this declaration, so we don't show documentation in that case.
146   if (Result.Kind != CodeCompletionResult::RK_Declaration)
147     return "";
148   auto Decl = Result.getDeclaration();
149   if (!Decl)
150     return "";
151   const RawComment *RC = getCompletionComment(Ctx, Decl);
152   if (!RC)
153     return "";
154   return getFormattedComment(Ctx, *RC, CommentsFromHeaders);
155 }
156 
157 std::string
158 getParameterDocComment(const ASTContext &Ctx,
159                        const CodeCompleteConsumer::OverloadCandidate &Result,
160                        unsigned ArgIndex, bool CommentsFromHeaders) {
161   auto Func = Result.getFunction();
162   if (!Func)
163     return "";
164   const RawComment *RC = getParameterComment(Ctx, Result, ArgIndex);
165   if (!RC)
166     return "";
167   return getFormattedComment(Ctx, *RC, CommentsFromHeaders);
168 }
169 
170 void getLabelAndInsertText(const CodeCompletionString &CCS, std::string *Label,
171                            std::string *InsertText, bool EnableSnippets) {
172   return EnableSnippets ? processSnippetChunks(CCS, Label, InsertText)
173                         : processPlainTextChunks(CCS, Label, InsertText);
174 }
175 
176 std::string formatDocumentation(const CodeCompletionString &CCS,
177                                 llvm::StringRef DocComment) {
178   // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this
179   // information in the documentation field.
180   std::string Result;
181   const unsigned AnnotationCount = CCS.getAnnotationCount();
182   if (AnnotationCount > 0) {
183     Result += "Annotation";
184     if (AnnotationCount == 1) {
185       Result += ": ";
186     } else /* AnnotationCount > 1 */ {
187       Result += "s: ";
188     }
189     for (unsigned I = 0; I < AnnotationCount; ++I) {
190       Result += CCS.getAnnotation(I);
191       Result.push_back(I == AnnotationCount - 1 ? '\n' : ' ');
192     }
193   }
194   // Add brief documentation (if there is any).
195   if (!DocComment.empty()) {
196     if (!Result.empty()) {
197       // This means we previously added annotations. Add an extra newline
198       // character to make the annotations stand out.
199       Result.push_back('\n');
200     }
201     Result += DocComment;
202   }
203   return Result;
204 }
205 
206 std::string getDetail(const CodeCompletionString &CCS) {
207   for (const auto &Chunk : CCS) {
208     // Informative qualifier chunks only clutter completion results, skip
209     // them.
210     switch (Chunk.Kind) {
211     case CodeCompletionString::CK_ResultType:
212       return Chunk.Text;
213     default:
214       break;
215     }
216   }
217   return "";
218 }
219 
220 std::string getFilterText(const CodeCompletionString &CCS) {
221   for (const auto &Chunk : CCS) {
222     switch (Chunk.Kind) {
223     case CodeCompletionString::CK_TypedText:
224       // There's always exactly one CK_TypedText chunk.
225       return Chunk.Text;
226     default:
227       break;
228     }
229   }
230   return "";
231 }
232 
233 } // namespace clangd
234 } // namespace clang
235