diff options
author | Douglas Gregor <dgregor@apple.com> | 2009-11-07 00:00:49 +0000 |
---|---|---|
committer | Douglas Gregor <dgregor@apple.com> | 2009-11-07 00:00:49 +0000 |
commit | 0c8296dfb495f41d6f0de6fe1d03014ffd063674 (patch) | |
tree | 0d9bfd8e8a4b996ec6fcd402bc052fc00e978514 | |
parent | 9e7e3c6c47f2819bce456950617a966d26839eb4 (diff) |
Various improvements to Clang's code-completion infrastructure:
- Introduce more code-completion string "chunk" kinds that describe
symbols, the actual text that the user is expected to type, etc.
- Make the generation of macro results optional, since it can be
slow
- Make code-completion accessible through the C API, marshalling the
code-completion results through a temporary file (ick) to maintain
process separation.
The last doesn't have tests yet.
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@86306 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r-- | include/clang-c/Index.h | 321 | ||||
-rw-r--r-- | include/clang/Sema/CodeCompleteConsumer.h | 121 | ||||
-rw-r--r-- | lib/Sema/CodeCompleteConsumer.cpp | 473 | ||||
-rw-r--r-- | lib/Sema/SemaCodeComplete.cpp | 113 | ||||
-rw-r--r-- | test/CodeCompletion/macros.c | 4 | ||||
-rw-r--r-- | tools/CIndex/CIndex.cpp | 315 | ||||
-rw-r--r-- | tools/CIndex/CIndex.exports | 4 | ||||
-rw-r--r-- | tools/c-index-test/c-index-test.c | 106 | ||||
-rw-r--r-- | tools/clang-cc/clang-cc.cpp | 32 |
9 files changed, 1424 insertions, 65 deletions
diff --git a/include/clang-c/Index.h b/include/clang-c/Index.h index 1a58f44ff4..7bdcab5292 100644 --- a/include/clang-c/Index.h +++ b/include/clang-c/Index.h @@ -305,6 +305,327 @@ CINDEX_LINKAGE void clang_getDefinitionSpellingAndExtent(CXCursor, */ CINDEX_LINKAGE CXDecl clang_getCursorDecl(CXCursor); +/** + * \brief A semantic string that describes a code-completion result. + * + * A semantic string that describes the formatting of a code-completion + * result as a single "template" of text that should be inserted into the + * source buffer when a particular code-completion result is selected. + * Each semantic string is made up of some number of "chunks", each of which + * contains some text along with a description of what that text means, e.g., + * the name of the entity being referenced, whether the text chunk is part of + * the template, or whether it is a "placeholder" that the user should replace + * with actual code,of a specific kind. See \c CXCompletionChunkKind for a + * description of the different kinds of chunks. + */ +typedef void *CXCompletionString; + +/** + * \brief A single result of code completion. + */ +typedef struct { + /** + * \brief The kind of entity that this completion refers to. + * + * The cursor kind will be a macro, keyword, or a declaration (one of the + * *Decl cursor kinds), describing the entity that the completion is + * referring to. + * + * \todo In the future, we would like to provide a full cursor, to allow + * the client to extract additional information from declaration. + */ + enum CXCursorKind CursorKind; + + /** + * \brief The code-completion string that describes how to insert this + * code-completion result into the editing buffer. + */ + CXCompletionString CompletionString; +} CXCompletionResult; + +/** + * \brief Describes a single piece of text within a code-completion string. + * + * Each "chunk" within a code-completion string (\c CXCompletionString) is + * either a piece of text with a specific "kind" that describes how that text + * should be interpreted by the client or is another completion string. + */ +enum CXCompletionChunkKind { + /** + * \brief A code-completion string that describes "optional" text that + * could be a part of the template (but is not required). + * + * The Optional chunk is the only kind of chunk that has a code-completion + * string for its representation, which is accessible via + * \c clang_getCompletionChunkCompletionString(). The code-completion string + * describes an additional part of the template that is completely optional. + * For example, optional chunks can be used to describe the placeholders for + * arguments that match up with defaulted function parameters, e.g. given: + * + * \code + * void f(int x, float y = 3.14, double z = 2.71828); + * \endcode + * + * The code-completion string for this function would contain: + * - a TypedText chunk for "f". + * - a LeftParen chunk for "(". + * - a Placeholder chunk for "int x" + * - an Optional chunk containing the remaining defaulted arguments, e.g., + * - a Comma chunk for "," + * - a Placeholder chunk for "float x" + * - an Optional chunk containing the last defaulted argument: + * - a Comma chunk for "," + * - a Placeholder chunk for "double z" + * - a RightParen chunk for ")" + * + * There are many ways two handle Optional chunks. Two simple approaches are: + * - Completely ignore optional chunks, in which case the template for the + * function "f" would only include the first parameter ("int x"). + * - Fully expand all optional chunks, in which case the template for the + * function "f" would have all of the parameters. + */ + CXCompletionChunk_Optional, + /** + * \brief Text that a user would be expected to type to get this + * code-completion result. + * + * There will be exactly one "typed text" chunk in a semantic string, which + * will typically provide the spelling of a keyword or the name of a + * declaration that could be used at the current code point. Clients are + * expected to filter the code-completion results based on the text in this + * chunk. + */ + CXCompletionChunk_TypedText, + /** + * \brief Text that should be inserted as part of a code-completion result. + * + * A "text" chunk represents text that is part of the template to be + * inserted into user code should this particular code-completion result + * be selected. + */ + CXCompletionChunk_Text, + /** + * \brief Placeholder text that should be replaced by the user. + * + * A "placeholder" chunk marks a place where the user should insert text + * into the code-completion template. For example, placeholders might mark + * the function parameters for a function declaration, to indicate that the + * user should provide arguments for each of those parameters. The actual + * text in a placeholder is a suggestion for the text to display before + * the user replaces the placeholder with real code. + */ + CXCompletionChunk_Placeholder, + /** + * \brief Informative text that should be displayed but never inserted as + * part of the template. + * + * An "informative" chunk contains annotations that can be displayed to + * help the user decide whether a particular code-completion result is the + * right option, but which is not part of the actual template to be inserted + * by code completion. + */ + CXCompletionChunk_Informative, + /** + * \brief Text that describes the current parameter when code-completion is + * referring to function call, message send, or template specialization. + * + * A "current parameter" chunk occurs when code-completion is providing + * information about a parameter corresponding to the argument at the + * code-completion point. For example, given a function + * + * \code + * int add(int x, int y); + * \endcode + * + * and the source code \c add(, where the code-completion point is after the + * "(", the code-completion string will contain a "current parameter" chunk + * for "int x", indicating that the current argument will initialize that + * parameter. After typing further, to \c add(17, (where the code-completion + * point is after the ","), the code-completion string will contain a + * "current paremeter" chunk to "int y". + */ + CXCompletionChunk_CurrentParameter, + /** + * \brief A left parenthesis ('('), used to initiate a function call or + * signal the beginning of a function parameter list. + */ + CXCompletionChunk_LeftParen, + /** + * \brief A right parenthesis (')'), used to finish a function call or + * signal the end of a function parameter list. + */ + CXCompletionChunk_RightParen, + /** + * \brief A left bracket ('['). + */ + CXCompletionChunk_LeftBracket, + /** + * \brief A right bracket (']'). + */ + CXCompletionChunk_RightBracket, + /** + * \brief A left brace ('{'). + */ + CXCompletionChunk_LeftBrace, + /** + * \brief A right brace ('}'). + */ + CXCompletionChunk_RightBrace, + /** + * \brief A left angle bracket ('<'). + */ + CXCompletionChunk_LeftAngle, + /** + * \brief A right angle bracket ('>'). + */ + CXCompletionChunk_RightAngle, + /** + * \brief A comma separator (','). + */ + CXCompletionChunk_Comma +}; + +/** + * \brief Callback function that receives a single code-completion result. + * + * This callback will be invoked by \c clang_codeComplete() for each + * code-completion result. + * + * \param completion_result a pointer to the current code-completion result, + * providing one possible completion. The pointer itself is only valid + * during the execution of the completion callback. + * + * \param client_data the client data provided to \c clang_codeComplete(). + */ +typedef void (*CXCompletionIterator)(CXCompletionResult *completion_result, + CXClientData client_data); + +/** + * \brief Determine the kind of a particular chunk within a completion string. + * + * \param completion_string the completion string to query. + * + * \param chunk_number the 0-based index of the chunk in the completion string. + * + * \returns the kind of the chunk at the index \c chunk_number. + */ +CINDEX_LINKAGE enum CXCompletionChunkKind +clang_getCompletionChunkKind(CXCompletionString completion_string, + unsigned chunk_number); + +/** + * \brief Retrieve the text associated with a particular chunk within a + * completion string. + * + * \param completion_string the completion string to query. + * + * \param chunk_number the 0-based index of the chunk in the completion string. + * + * \returns the text associated with the chunk at index \c chunk_number. + */ +CINDEX_LINKAGE const char * +clang_getCompletionChunkText(CXCompletionString completion_string, + unsigned chunk_number); + +/** + * \brief Retrieve the completion string associated with a particular chunk + * within a completion string. + * + * \param completion_string the completion string to query. + * + * \param chunk_number the 0-based index of the chunk in the completion string. + * + * \returns the completion string associated with the chunk at index + * \c chunk_number, or NULL if that chunk is not represented by a completion + * string. + */ +CINDEX_LINKAGE CXCompletionString +clang_getCompletionChunkCompletionString(CXCompletionString completion_string, + unsigned chunk_number); + +/** + * \brief Retrieve the number of chunks in the given code-completion string. + */ +CINDEX_LINKAGE unsigned +clang_getNumCompletionChunks(CXCompletionString completion_string); + +/** + * \brief Perform code completion at a given location in a source file. + * + * This function performs code completion at a particular file, line, and + * column within source code, providing results that suggest potential + * code snippets based on the context of the completion. The basic model + * for code completion is that Clang will parse a complete source file, + * performing syntax checking up to the location where code-completion has + * been requested. At that point, a special code-completion token is passed + * to the parser, which recognizes this token and determines, based on the + * current location in the C/Objective-C/C++ grammar and the state of + * semantic analysis, what completions to provide. These completions are + * enumerated through a callback interface to the client. + * + * Code completion itself is meant to be triggered by the client when the + * user types punctuation characters or whitespace, at which point the + * code-completion location will coincide with the cursor. For example, if \c p + * is a pointer, code-completion might be triggered after the "-" and then + * after the ">" in \c p->. When the code-completion location is afer the ">", + * the completion results will provide, e.g., the members of the struct that + * "p" points to. The client is responsible for placing the cursor at the + * beginning of the token currently being typed, then filtering the results + * based on the contents of the token. For example, when code-completing for + * the expression \c p->get, the client should provide the location just after + * the ">" (e.g., pointing at the "g") to this code-completion hook. Then, the + * client can filter the results based on the current token text ("get"), only + * showing those results that start with "get". The intent of this interface + * is to separate the relatively high-latency acquisition of code-competion + * results from the filtering of results on a per-character basis, which must + * have a lower latency. + * + * \param CIdx the \c CXIndex instance that will be used to perform code + * completion. + * + * \param source_filename the name of the source file that should be parsed + * to perform code-completion. This source file must be the same as or + * include the filename described by \p complete_filename, or no code-completion + * results will be produced. + * + * \param num_command_line_args the number of command-line arguments stored in + * \p command_line_args. + * + * \param command_line_args the command-line arguments to pass to the Clang + * compiler to build the given source file. This should include all of the + * necessary include paths, language-dialect switches, precompiled header + * includes, etc., but should not include any information specific to + * code completion. + * + * \param complete_filename the name of the source file where code completion + * should be performed. In many cases, this name will be the same as the + * source filename. However, the completion filename may also be a file + * included by the source file, which is required when producing + * code-completion results for a header. + * + * \param complete_line the line at which code-completion should occur. + * + * \param complete_column the column at which code-completion should occur. + * Note that the column should point just after the syntactic construct that + * initiated code completion, and not in the middle of a lexical token. + * + * \param completion_iterator a callback function that will receive + * code-completion results. + * + * \param client_data client-specific data that will be passed back via the + * code-completion callback function. + */ +CINDEX_LINKAGE void clang_codeComplete(CXIndex CIdx, + const char *source_filename, + int num_command_line_args, + const char **command_line_args, + const char *complete_filename, + unsigned complete_line, + unsigned complete_column, + CXCompletionIterator completion_iterator, + CXClientData client_data); + + #ifdef __cplusplus } #endif diff --git a/include/clang/Sema/CodeCompleteConsumer.h b/include/clang/Sema/CodeCompleteConsumer.h index 5b3522c9eb..9bbf11b162 100644 --- a/include/clang/Sema/CodeCompleteConsumer.h +++ b/include/clang/Sema/CodeCompleteConsumer.h @@ -14,6 +14,7 @@ #define LLVM_CLANG_SEMA_CODECOMPLETECONSUMER_H #include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" #include <memory> #include <string> @@ -43,6 +44,10 @@ public: /// \brief The different kinds of "chunks" that can occur within a code /// completion string. enum ChunkKind { + /// \brief The piece of text that the user is expected to type to + /// match the code-completion string, typically a keyword or the name of a + /// declarator or macro. + CK_TypedText, /// \brief A piece of text that should be placed in the buffer, e.g., /// parentheses or a comma in a function call. CK_Text, @@ -55,7 +60,29 @@ public: CK_Placeholder, /// \brief A piece of text that describes something about the result but /// should not be inserted into the buffer. - CK_Informative + CK_Informative, + /// \brief A piece of text that describes the parameter that corresponds + /// to the code-completion location within a function call, message send, + /// macro invocation, etc. + CK_CurrentParameter, + /// \brief A left parenthesis ('('). + CK_LeftParen, + /// \brief A right parenthesis (')'). + CK_RightParen, + /// \brief A left bracket ('['). + CK_LeftBracket, + /// \brief A right bracket (']'). + CK_RightBracket, + /// \brief A left brace ('{'). + CK_LeftBrace, + /// \brief A right brace ('}'). + CK_RightBrace, + /// \brief A left angle bracket ('<'). + CK_LeftAngle, + /// \brief A right angle bracket ('>'). + CK_RightAngle, + /// \brief A comma separator (','). + CK_Comma }; /// \brief One piece of the code completion string. @@ -66,7 +93,7 @@ public: union { /// \brief The text string associated with a CK_Text, CK_Placeholder, - /// or CK_Informative chunk. + /// CK_Informative, or CK_Comma chunk. /// The string is owned by the chunk and will be deallocated /// (with delete[]) when the chunk is destroyed. const char *Text; @@ -79,10 +106,8 @@ public: Chunk() : Kind(CK_Text), Text(0) { } - private: - Chunk(ChunkKind Kind, const char *Text); - - public: + Chunk(ChunkKind Kind, llvm::StringRef Text = 0); + /// \brief Create a new text chunk. static Chunk CreateText(const char *Text); @@ -95,6 +120,9 @@ public: /// \brief Create a new informative chunk. static Chunk CreateInformative(const char *Informative); + /// \brief Create a new current-parameter chunk. + static Chunk CreateCurrentParameter(const char *CurrentParameter); + /// \brief Destroy this chunk, deallocating any memory it owns. void Destroy(); }; @@ -113,6 +141,24 @@ public: typedef llvm::SmallVector<Chunk, 4>::const_iterator iterator; iterator begin() const { return Chunks.begin(); } iterator end() const { return Chunks.end(); } + bool empty() const { return Chunks.empty(); } + unsigned size() const { return Chunks.size(); } + + Chunk &operator[](unsigned I) { + assert(I < size() && "Chunk index out-of-range"); + return Chunks[I]; + } + + const Chunk &operator[](unsigned I) const { + assert(I < size() && "Chunk index out-of-range"); + return Chunks[I]; + } + + /// \brief Add a new typed-text chunk. + /// The text string will be copied. + void AddTypedTextChunk(const char *Text) { + Chunks.push_back(Chunk(CK_TypedText, Text)); + } /// \brief Add a new text chunk. /// The text string will be copied. @@ -136,15 +182,37 @@ public: void AddInformativeChunk(const char *Text) { Chunks.push_back(Chunk::CreateInformative(Text)); } + + /// \brief Add a new current-parameter chunk. + /// The text will be copied. + void AddCurrentParameterChunk(const char *CurrentParameter) { + Chunks.push_back(Chunk::CreateCurrentParameter(CurrentParameter)); + } + + /// \brief Add a new chunk. + void AddChunk(Chunk C) { Chunks.push_back(C); } /// \brief Retrieve a string representation of the code completion string, /// which is mainly useful for debugging. - std::string getAsString() const; + std::string getAsString() const; + + /// \brief Serialize this code-completion string to the given stream. + void Serialize(llvm::raw_ostream &OS) const; + + /// \brief Deserialize a code-completion string from the given string. + static CodeCompletionString *Deserialize(llvm::StringRef &Str); }; +llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const CodeCompletionString &CCS); + /// \brief Abstract interface for a consumer of code-completion /// information. class CodeCompleteConsumer { +protected: + /// \brief Whether to include macros in the code-completion results. + bool IncludeMacros; + public: /// \brief Captures a result of code completion. struct Result { @@ -291,6 +359,14 @@ public: Sema &S) const; }; + CodeCompleteConsumer() : IncludeMacros(false) { } + + explicit CodeCompleteConsumer(bool IncludeMacros) + : IncludeMacros(IncludeMacros) { } + + /// \brief Whether the code-completion consumer wants to see macros. + bool includeMacros() const { return IncludeMacros; } + /// \brief Deregisters and destroys this code-completion consumer. virtual ~CodeCompleteConsumer(); @@ -326,8 +402,35 @@ class PrintingCodeCompleteConsumer : public CodeCompleteConsumer { public: /// \brief Create a new printing code-completion consumer that prints its /// results to the given raw output stream. - PrintingCodeCompleteConsumer(Sema &S, llvm::raw_ostream &OS) - : SemaRef(S), OS(OS) { } + PrintingCodeCompleteConsumer(Sema &S, bool IncludeMacros, + llvm::raw_ostream &OS) + : CodeCompleteConsumer(IncludeMacros), SemaRef(S), OS(OS) { } + + /// \brief Prints the finalized code-completion results. + virtual void ProcessCodeCompleteResults(Result *Results, + unsigned NumResults); + + virtual void ProcessOverloadCandidates(unsigned CurrentArg, + OverloadCandidate *Candidates, + unsigned NumCandidates); +}; + +/// \brief A code-completion consumer that prints the results it receives +/// in a format that is parsable by the CIndex library. +class CIndexCodeCompleteConsumer : public CodeCompleteConsumer { + /// \brief The semantic-analysis object to which this code-completion + /// consumer is attached. + Sema &SemaRef; + + /// \brief The raw output stream. + llvm::raw_ostream &OS; + +public: + /// \brief Create a new CIndex code-completion consumer that prints its + /// results to the given raw output stream in a format readable to the CIndex + /// library. + CIndexCodeCompleteConsumer(Sema &S, bool IncludeMacros, llvm::raw_ostream &OS) + : CodeCompleteConsumer(IncludeMacros), SemaRef(S), OS(OS) { } /// \brief Prints the finalized code-completion results. virtual void ProcessCodeCompleteResults(Result *Results, diff --git a/lib/Sema/CodeCompleteConsumer.cpp b/lib/Sema/CodeCompleteConsumer.cpp index 9b24d55f3e..cb4e8ef826 100644 --- a/lib/Sema/CodeCompleteConsumer.cpp +++ b/lib/Sema/CodeCompleteConsumer.cpp @@ -16,6 +16,7 @@ #include "clang/Lex/Preprocessor.h" #include "Sema.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringSwitch.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/raw_ostream.h" #include <algorithm> @@ -26,14 +27,62 @@ using namespace clang; //===----------------------------------------------------------------------===// // Code completion string implementation //===----------------------------------------------------------------------===// -CodeCompletionString::Chunk::Chunk(ChunkKind Kind, const char *Text) +CodeCompletionString::Chunk::Chunk(ChunkKind Kind, llvm::StringRef Text) : Kind(Kind), Text(0) { - assert((Kind == CK_Text || Kind == CK_Placeholder || Kind == CK_Informative) - && "Invalid text chunk kind"); - char *New = new char [std::strlen(Text) + 1]; - std::strcpy(New, Text); - this->Text = New; + switch (Kind) { + case CK_TypedText: + case CK_Text: + case CK_Placeholder: + case CK_Informative: + case CK_CurrentParameter: { + char *New = new char [Text.size() + 1]; + std::memcpy(New, Text.data(), Text.size()); + New[Text.size()] = '\0'; + this->Text = New; + break; + } + + case CK_Optional: + llvm::llvm_unreachable("Optional strings cannot be created from text"); + break; + + case CK_LeftParen: + this->Text = "("; + break; + + case CK_RightParen: + this->Text = ")"; + break; + + case CK_LeftBracket: + this->Text = "["; + break; + + case CK_RightBracket: + this->Text = "]"; + break; + + case CK_LeftBrace: + this->Text = "{"; + break; + + case CK_RightBrace: + this->Text = "}"; + break; + + case CK_LeftAngle: + this->Text = "<"; + break; + + case CK_RightAngle: + this->Text = ">"; + break; + + case CK_Comma: + this->Text = ", "; + break; + } } CodeCompletionString::Chunk @@ -60,6 +109,13 @@ CodeCompletionString::Chunk::CreateInformative(const char *Informative) { return Chunk(CK_Informative, Informative); } +CodeCompletionString::Chunk +CodeCompletionString::Chunk::CreateCurrentParameter( + const char *CurrentParameter) { + return Chunk(CK_CurrentParameter, CurrentParameter); +} + + void CodeCompletionString::Chunk::Destroy() { switch (Kind) { @@ -67,10 +123,23 @@ CodeCompletionString::Chunk::Destroy() { delete Optional; break; + case CK_TypedText: case CK_Text: case CK_Placeholder: case CK_Informative: - delete [] Text; + case CK_CurrentParameter: + delete [] Text; + break; + + case CK_LeftParen: + case CK_RightParen: + case CK_LeftBracket: + case CK_RightBracket: + case CK_LeftBrace: + case CK_RightBrace: + case CK_LeftAngle: + case CK_RightAngle: + case CK_Comma: break; } } @@ -86,16 +155,322 @@ std::string CodeCompletionString::getAsString() const { for (iterator C = begin(), CEnd = end(); C != CEnd; ++C) { switch (C->Kind) { - case CK_Text: OS << C->Text; break; case CK_Optional: OS << "{#" << C->Optional->getAsString() << "#}"; break; case CK_Placeholder: OS << "<#" << C->Text << "#>"; break; case CK_Informative: OS << "[#" << C->Text << "#]"; break; + case CK_CurrentParameter: OS << "<#" << C->Text << "#>"; break; + default: OS << C->Text; break; } } OS.flush(); return Result; } + +namespace { + // Escape a string for XML-like formatting. + struct EscapedString { + EscapedString(llvm::StringRef Str) : Str(Str) { } + + llvm::StringRef Str; + }; + + llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, EscapedString EStr) { + llvm::StringRef Str = EStr.Str; + while (!Str.empty()) { + // Find the next escaped character. + llvm::StringRef::size_type Pos = Str.find_first_of("<>&\"'"); + + // Print everything before that escaped character. + OS << Str.substr(0, Pos); + + // If we didn't find any escaped characters, we're done. + if (Pos == llvm::StringRef::npos) + break; + + // Print the appropriate escape sequence. + switch (Str[Pos]) { + case '<': OS << "<"; break; + case '>': OS << ">"; break; + case '&': OS << "&"; break; + case '"': OS << """; break; + case '\'': OS << "'"; break; + } + + // Remove everything up to and including that escaped character. + Str = Str.substr(Pos + 1); + } + + return OS; + } + + /// \brief Remove XML-like escaping from a string. + std::string UnescapeString(llvm::StringRef Str) { + using llvm::StringRef; + + std::string Result; + llvm::raw_string_ostream OS(Result); + + while (!Str.empty()) { + StringRef::size_type Amp = Str.find('&'); + OS << Str.substr(0, Amp); + + if (Amp == StringRef::npos) + break; + + StringRef::size_type Semi = Str.substr(Amp).find(';'); + if (Semi == StringRef::npos) { + // Malformed input; do the best we can. + OS << '&'; + Str = Str.substr(Amp + 1); + continue; + } + + char Unescaped = llvm::StringSwitch<char>(Str.substr(Amp + 1, Semi - 1)) + .Case("lt", '<') + .Case("gt", '>') + .Case("amp", '&') + .Case("quot", '"') + .Case("apos", '\'') + .Default('\0'); + + if (Unescaped) + OS << Unescaped; + else + OS << Str.substr(Amp, Semi + 1); + Str = Str.substr(Amp + Semi + 1); + } + + return OS.str(); + } +} + +void CodeCompletionString::Serialize(llvm::raw_ostream &OS) const { + for (iterator C = begin(), CEnd = end(); C != CEnd; ++C) { + switch (C->Kind) { + case CK_TypedText: + OS << "<typed-text>" << EscapedString(C->Text) << "</>"; + break; + case CK_Text: + OS << "<text>" << EscapedString(C->Text) << "</>"; + break; + case CK_Optional: + OS << "<optional>"; + C->Optional->Serialize(OS); + OS << "</>"; + break; + case CK_Placeholder: + OS << "<placeholder>" << EscapedString(C->Text) << "</>"; + break; + case CK_Informative: + OS << "<informative>" << EscapedString(C->Text) << "</>"; + break; + case CK_CurrentParameter: + OS << "<current-parameter>" << EscapedString(C->Text) << "</>"; + break; + case CK_LeftParen: + OS << "<lparen/>"; + break; + case CK_RightParen: + OS << "<rparen/>"; + break; + case CK_LeftBracket: + OS << "<lbracket/>"; + break; + case CK_RightBracket: + OS << "<rbracket/>"; + break; + case CK_LeftBrace: + OS << "<lbrace/>"; + break; + case CK_RightBrace: + OS << "<rbrace/>"; + break; + case CK_LeftAngle: + OS << "<langle/>"; + break; + case CK_RightAngle: + OS << "<rangle/>"; + break; + case CK_Comma: + OS << "<comma/>"; + break; + } + } +} + +/// \brief Parse the next XML-ish tag of the form <blah>. +/// +/// \param Str the string in which we're looking for the next tag. +/// +/// \param TagPos if successful, will be set to the start of the tag we found. +/// +/// \param Standalone will indicate whether this is a "standalone" tag that +/// has no associated data, e.g., <comma/>. +/// +/// \param Terminator will indicate whether this is a terminating tag (that is +/// or starts with '/'). +/// +/// \returns the tag itself, without the angle brackets. +static llvm::StringRef ParseNextTag(llvm::StringRef Str, + llvm::StringRef::size_type &StartTag, + llvm::StringRef::size_type &AfterTag, + bool &Standalone, bool &Terminator) { + using llvm::StringRef; + + Standalone = false; + Terminator = false; + AfterTag = StringRef::npos; + + // Find the starting '<'. + StartTag = Str.find('<'); + if (StartTag == StringRef::npos) + return llvm::StringRef(); + + // Find the corresponding '>'. + llvm::StringRef::size_type EndTag = Str.substr(StartTag).find('>'); + if (EndTag == StringRef::npos) + return llvm::StringRef(); + AfterTag = StartTag + EndTag + 1; + + // Determine whether this is a terminating tag. + if (Str[StartTag + 1] == '/') { + Terminator = true; + Str = Str.substr(1); + --EndTag; + } + + // Determine whether this is a standalone tag. + if (!Terminator && Str[StartTag + EndTag - 1] == '/') { + Standalone = true; + if (EndTag > 1) + --EndTag; + } + + return Str.substr(StartTag + 1, EndTag - 1); +} + +CodeCompletionString *CodeCompletionString::Deserialize(llvm::StringRef &Str) { + using llvm::StringRef; + + CodeCompletionString *Result = new CodeCompletionString; + + do { + // Parse the next tag. + StringRef::size_type StartTag, AfterTag; + bool Standalone, Terminator; + StringRef Tag = ParseNextTag(Str, StartTag, AfterTag, Standalone, + Terminator); + + if (StartTag == StringRef::npos) + break; + + // Figure out what kind of chunk we have. + const unsigned UnknownKind = 10000; + unsigned Kind = llvm::StringSwitch<unsigned>(Tag) + .Case("typed-text", CK_TypedText) + .Case("text", CK_Text) + .Case("optional", CK_Optional) + .Case("placeholder", CK_Placeholder) + .Case("informative", CK_Informative) + .Case("current-parameter", CK_CurrentParameter) + .Case("lparen", CK_LeftParen) + .Case("rparen", CK_RightParen) + .Case("lbracket", CK_LeftBracket) + .Case("rbracket", CK_RightBracket) + .Case("lbrace", CK_LeftBrace) + .Case("rbrace", CK_RightBrace) + .Case("langle", CK_LeftAngle) + .Case("rangle", CK_RightAngle) + .Case("comma", CK_Comma) + .Default(UnknownKind); + + // If we've hit a terminator tag, we're done. + if (Terminator) + break; + + // Consume the tag. + Str = Str.substr(AfterTag); + + // Handle standalone tags now, since they don't need to be matched to + // anything. + if (Standalone) { + // Ignore anything we don't know about. + if (Kind == UnknownKind) + continue; + + switch ((ChunkKind)Kind) { + case CK_TypedText: + case CK_Text: + case CK_Optional: + case CK_Placeholder: + case CK_Informative: + case CK_CurrentParameter: + // There is no point in creating empty chunks of these kinds. + break; + + case CK_LeftParen: + case CK_RightParen: + case CK_LeftBracket: + case CK_RightBracket: + case CK_LeftBrace: + case CK_RightBrace: + case CK_LeftAngle: + case CK_RightAngle: + case CK_Comma: + Result->AddChunk(Chunk((ChunkKind)Kind)); + break; + } + + continue; + } + + if (Kind == CK_Optional) { + // Deserialize the optional code-completion string. + std::auto_ptr<CodeCompletionString> Optional(Deserialize(Str)); + Result->AddOptionalChunk(Optional); + } + + StringRef EndTag = ParseNextTag(Str, StartTag, AfterTag, Standalone, + Terminator); + if (StartTag == StringRef::npos || !Terminator || Standalone) + break; // Parsing failed; just give up. + + if (EndTag.empty() || Tag == EndTag) { + // Found the matching end tag. Add this chunk based on the text + // between the tags, then consume that input. + StringRef Text = Str.substr(0, StartTag); + switch ((ChunkKind)Kind) { + case CK_TypedText: + case CK_Text: + case CK_Placeholder: + case CK_Informative: + case CK_CurrentParameter: + case CK_LeftParen: + case CK_RightParen: + case CK_LeftBracket: + case CK_RightBracket: + case CK_LeftBrace: + case CK_RightBrace: + case CK_LeftAngle: + case CK_RightAngle: + case CK_Comma: + Result->AddChunk(Chunk((ChunkKind)Kind, UnescapeString(Text))); + break; + + case CK_Optional: + // We've already added the optional chunk. + break; + } + } + + // Remove this tag. + Str = Str.substr(AfterTag); + } while (!Str.empty()); + + return Result; +} + //===----------------------------------------------------------------------===// // Code completion overload candidate implementation //===----------------------------------------------------------------------===// @@ -193,3 +568,85 @@ PrintingCodeCompleteConsumer::ProcessOverloadCandidates(unsigned CurrentArg, // FIXME: Move this somewhere else! SemaRef.PP.getDiagnostics().setSuppressAllDiagnostics(); } + +void +CIndexCodeCompleteConsumer::ProcessCodeCompleteResults(Result *Results, + unsigned NumResults) { + // Print the results. + for (unsigned I = 0; I != NumResults; ++I) { + OS << "COMPLETION:" << Results[I].Rank << ":"; + switch (Results[I].Kind) { + case Result::RK_Declaration: + if (RecordDecl *Record = dyn_cast<RecordDecl>(Results[I].Declaration)) { + if (Record->isStruct()) + OS << "Struct:"; + else if (Record->isUnion()) + OS << "Union:"; + else + OS << "Class:"; + } else if (ObjCMethodDecl *Method + = dyn_cast<ObjCMethodDecl>(Results[I].Declaration)) { + if (Method->isInstanceMethod()) + OS << "ObjCInstanceMethod:"; + else + OS << "ObjCClassMethod:"; + } else { + OS << Results[I].Declaration->getDeclKindName() << ":"; + } + if (CodeCompletionString *CCS + = Results[I].CreateCodeCompletionString(SemaRef)) { + CCS->Serialize(OS); + delete CCS; + } else { + OS << "<typed-text>" + << Results[I].Declaration->getNameAsString() + << "</>"; + } + + OS << '\n'; + break; + + case Result::RK_Keyword: + OS << "Keyword:<typed-text>" << Results[I].Keyword << "</>\n"; + break; + + case Result::RK_Macro: { + OS << "Macro:"; + if (CodeCompletionString *CCS + = Results[I].CreateCodeCompletionString(SemaRef)) { + CCS->Serialize(OS); + delete CCS; + } else { + OS << "<typed-text>" << Results[I].Macro->getName() << "</>"; + } + OS << '\n'; + break; + } + } + } + + // Once we've printed the code-completion results, suppress remaining + // diagnostics. + // FIXME: Move this somewhere else! + SemaRef.PP.getDiagnostics().setSuppressAllDiagnostics(); +} + +void +CIndexCodeCompleteConsumer::ProcessOverloadCandidates(unsigned CurrentArg, + OverloadCandidate *Candidates, + unsigned NumCandidates) { + for (unsigned I = 0; I != NumCandidates; ++I) { + if (CodeCompletionString *CCS + = Candidates[I].CreateSignatureString(CurrentArg, SemaRef)) { + OS << "OVERLOAD:"; + CCS->Serialize(OS); + OS << '\n'; + delete CCS; + } + } + + // Once we've printed the code-completion results, suppress remaining + // diagnostics. + // FIXME: Move this somewhere else! + SemaRef.PP.getDiagnostics().setSuppressAllDiagnostics(); +} diff --git a/lib/Sema/SemaCodeComplete.cpp b/lib/Sema/SemaCodeComplete.cpp index e9df17d6a1..9963fc3d45 100644 --- a/lib/Sema/SemaCodeComplete.cpp +++ b/lib/Sema/SemaCodeComplete.cpp @@ -187,8 +187,7 @@ getRequiredQualification(ASTContext &Context, Context.getTypeDeclType(TD).getTypePtr()); else assert(Parent->isTranslationUnit()); - } - + } return Result; } @@ -674,6 +673,8 @@ static void AddTypeSpecifierResults(const LangOptions &LangOpts, unsigned Rank, static void AddFunctionParameterChunks(ASTContext &Context, FunctionDecl *Function, CodeCompletionString *Result) { + typedef CodeCompletionString::Chunk Chunk; + CodeCompletionString *CCStr = Result; for (unsigned P = 0, N = Function->getNumParams(); P != N; ++P) { @@ -688,7 +689,7 @@ static void AddFunctionParameterChunks(ASTContext &Context, } if (P != 0) - CCStr->AddTextChunk(", "); + CCStr->AddChunk(Chunk(CodeCompletionString::CK_Comma)); // Format the placeholder string. std::string PlaceholderStr; @@ -713,6 +714,8 @@ static void AddTemplateParameterChunks(ASTContext &Context, TemplateDecl *Template, CodeCompletionString *Result, unsigned MaxParameters = 0) { + typedef CodeCompletionString::Chunk Chunk; + CodeCompletionString *CCStr = Result; bool FirstParameter = true; @@ -768,7 +771,7 @@ static void AddTemplateParameterChunks(ASTContext &Context, if (FirstParameter) FirstParameter = false; else - CCStr->AddTextChunk(", "); + CCStr->AddChunk(Chunk(CodeCompletionString::CK_Comma)); // Add the placeholder string. CCStr->AddPlaceholderChunk(PlaceholderStr.c_str()); @@ -803,6 +806,8 @@ void AddQualifierToCompletionString(CodeCompletionString *Result, /// result is all that is needed. CodeCompletionString * CodeCompleteConsumer::Result::CreateCodeCompletionString(Sema &S) { + typedef CodeCompletionString::Chunk Chunk; + if (Kind == RK_Keyword) return 0; @@ -813,12 +818,12 @@ CodeCompleteConsumer::Result::CreateCodeCompletionString(Sema &S) { // Format a function-like macro with placeholders for the arguments. CodeCompletionString *Result = new CodeCompletionString; - Result->AddTextChunk(Macro->getName().str().c_str()); - Result->AddTextChunk("("); + Result->AddTypedTextChunk(Macro->getName().str().c_str()); + Result->AddChunk(Chunk(CodeCompletionString::CK_LeftParen)); for (MacroInfo::arg_iterator A = MI->arg_begin(), AEnd = MI->arg_end(); A != AEnd; ++A) { if (A != MI->arg_begin()) - Result->AddTextChunk(", "); + Result->AddChunk(Chunk(CodeCompletionString::CK_Comma)); if (!MI->isVariadic() || A != AEnd - 1) { // Non-variadic argument. @@ -837,21 +842,28 @@ CodeCompleteConsumer::Result::CreateCodeCompletionString(Sema &S) { Result->AddPlaceholderChunk(Arg.c_str()); } } - Result->AddTextChunk(")"); + Result->AddChunk(Chunk(CodeCompletionString::CK_RightParen)); return Result; } assert(Kind == RK_Declaration && "Missed a macro kind?"); NamedDecl *ND = Declaration; + if (StartsNestedNameSpecifier) { + CodeCompletionString *Result = new CodeCompletionString; + Result->AddTypedTextChunk(ND->getNameAsString().c_str()); + Result->AddTextChunk("::"); + return Result; + } + if (FunctionDecl *Function = dyn_cast<FunctionDecl>(ND)) { CodeCompletionString *Result = new CodeCompletionString; AddQualifierToCompletionString(Result, Qualifier, QualifierIsInformative, S.Context); - Result->AddTextChunk(Function->getNameAsString().c_str()); - Result->AddTextChunk("("); + Result->AddTypedTextChunk(Function->getNameAsString().c_str()); + Result->AddChunk(Chunk(CodeCompletionString::CK_LeftParen)); AddFunctionParameterChunks(S.Context, Function, Result); - Result->AddTextChunk(")"); + Result->AddChunk(Chunk(CodeCompletionString::CK_RightParen)); return Result; } @@ -860,7 +872,7 @@ CodeCompleteConsumer::Result::CreateCodeCompletionString(Sema &S) { AddQualifierToCompletionString(Result, Qualifier, QualifierIsInformative, S.Context); FunctionDecl *Function = FunTmpl->getTemplatedDecl(); - Result->AddTextChunk(Function->getNameAsString().c_str()); + Result->AddTypedTextChunk(Function->getNameAsString().c_str()); // Figure out which template parameters are deduced (or have default // arguments). @@ -884,7 +896,7 @@ CodeCompleteConsumer::Result::CreateCodeCompletionString(Sema &S) { else { assert(isa<TemplateTemplateParmDecl>(Param)); HasDefaultArg - = cast<TemplateTemplateParmDecl>(Param)->hasDefaultArgument(); + = cast<TemplateTemplateParmDecl>(Param)->hasDefaultArgument(); } if (!HasDefaultArg) @@ -896,16 +908,16 @@ CodeCompleteConsumer::Result::CreateCodeCompletionString(Sema &S) { // Some of the function template arguments cannot be deduced from a // function call, so we introduce an explicit template argument list // containing all of the arguments up to the first deducible argument. - Result->AddTextChunk("<"); + Result->AddChunk(Chunk(CodeCompletionString::CK_LeftAngle)); AddTemplateParameterChunks(S.Context, FunTmpl, Result, LastDeducibleArgument); - Result->AddTextChunk(">"); + Result->AddChunk(Chunk(CodeCompletionString::CK_RightAngle)); } // Add the function parameters - Result->AddTextChunk("("); + Result->AddChunk(Chunk(CodeCompletionString::CK_LeftParen)); AddFunctionParameterChunks(S.Context, Function, Result); - Result->AddTextChunk(")"); + Result->AddChunk(Chunk(CodeCompletionString::CK_RightParen)); return Result; } @@ -913,20 +925,18 @@ CodeCompleteConsumer::Result::CreateCodeCompletionString(Sema &S) { CodeCompletionString *Result = new CodeCompletionString; AddQualifierToCompletionString(Result, Qualifier, QualifierIsInformative, S.Context); - Result->AddTextChunk(Template->getNameAsString().c_str()); - Result->AddTextChunk("<"); + Result->AddTypedTextChunk(Template->getNameAsString().c_str()); + Result->AddChunk(Chunk(CodeCompletionString::CK_LeftAngle)); AddTemplateParameterChunks(S.Context, Template, Result); - Result->AddTextChunk(">"); + Result->AddChunk(Chunk(CodeCompletionString::CK_RightAngle)); return Result; } - if (Qualifier || StartsNestedNameSpecifier) { + if (Qualifier) { CodeCompletionString *Result = new CodeCompletionString; AddQualifierToCompletionString(Result, Qualifier, QualifierIsInformative, S.Context); - Result->AddTextChunk(ND->getNameAsString().c_str()); - if (StartsNestedNameSpecifier) - Result->AddTextChunk("::"); + Result->AddTypedTextChunk(ND->getNameAsString().c_str()); return Result; } @@ -937,6 +947,8 @@ CodeCompletionString * CodeCompleteConsumer::OverloadCandidate::CreateSignatureString( unsigned CurrentArg, Sema &S) const { + typedef CodeCompletionString::Chunk Chunk; + CodeCompletionString *Result = new CodeCompletionString; FunctionDecl *FDecl = getFunction(); const FunctionProtoType *Proto @@ -947,9 +959,9 @@ CodeCompleteConsumer::OverloadCandidate::CreateSignatureString( const FunctionType *FT = getFunctionType(); Result->AddTextChunk( FT->getResultType().getAsString(S.Context.PrintingPolicy).c_str()); - Result->AddTextChunk("("); - Result->AddPlaceholderChunk("..."); - Result->AddTextChunk("("); + Result->AddChunk(Chunk(CodeCompletionString::CK_LeftParen)); + Result->AddChunk(Chunk(CodeCompletionString::CK_CurrentParameter, "...")); + Result->AddChunk(Chunk(CodeCompletionString::CK_RightParen)); return Result; } @@ -959,11 +971,11 @@ CodeCompleteConsumer::OverloadCandidate::CreateSignatureString( Result->AddTextChunk( Proto->getResultType().getAsString(S.Context.PrintingPolicy).c_str()); - Result->AddTextChunk("("); + Result->AddChunk(Chunk(CodeCompletionString::CK_LeftParen)); unsigned NumParams = FDecl? FDecl->getNumParams() : Proto->getNumArgs(); for (unsigned I = 0; I != NumParams; ++I) { if (I) - Result->AddTextChunk(", "); + Result->AddChunk(Chunk(CodeCompletionString::CK_Comma)); std::string ArgString; QualType ArgType; @@ -978,19 +990,20 @@ CodeCompleteConsumer::OverloadCandidate::CreateSignatureString( ArgType.getAsStringInternal(ArgString, S.Context.PrintingPolicy); if (I == CurrentArg) - Result->AddPlaceholderChunk(ArgString.c_str()); + Result->AddChunk(Chunk(CodeCompletionString::CK_CurrentParameter, + ArgString.c_str())); else Result->AddTextChunk(ArgString.c_str()); } if (Proto && Proto->isVariadic()) { - Result->AddTextChunk(", "); + Result->AddChunk(Chunk(CodeCompletionString::CK_Comma)); if (CurrentArg < NumParams) Result->AddTextChunk("..."); else - Result->AddPlaceholderChunk("..."); + Result->AddChunk(Chunk(CodeCompletionString::CK_CurrentParameter, "...")); } - Result->AddTextChunk(")"); + Result->AddChunk(Chunk(CodeCompletionString::CK_RightParen)); return Result; } @@ -1049,11 +1062,11 @@ namespace { }; } -// Add all of the known macros as code-completion results. static void AddMacroResults(Preprocessor &PP, unsigned Rank, ResultBuilder &Results) { Results.EnterNewScope(); - for (Preprocessor::macro_iterator M = PP.macro_begin(), MEnd = PP.macro_end(); + for (Preprocessor::macro_iterator M = PP.macro_begin(), + MEnd = PP.macro_end(); M != MEnd; ++M) Results.MaybeAddResult(CodeCompleteConsumer::Result(M->first, Rank)); Results.ExitScope(); @@ -1073,7 +1086,8 @@ void Sema::CodeCompleteOrdinaryName(Scope *S) { ResultBuilder Results(*this, &ResultBuilder::IsOrdinaryName); unsigned NextRank = CollectLookupResults(S, Context.getTranslationUnitDecl(), 0, CurContext, Results); - AddMacroResults(PP, NextRank, Results); + if (CodeCompleter->includeMacros()) + AddMacroResults(PP, NextRank, Results); HandleCodeCompleteResults(CodeCompleter, Results.data(), Results.size()); } @@ -1130,7 +1144,8 @@ void Sema::CodeCompleteMemberReferenceExpr(Scope *S, ExprTy *BaseE, } // Add macros - AddMacroResults(PP, NextRank, Results); + if (CodeCompleter->includeMacros()) + AddMacroResults(PP, NextRank, Results); // Hand off the results found for code completion. HandleCodeCompleteResults(CodeCompleter, Results.data(), Results.size()); @@ -1177,7 +1192,8 @@ void Sema::CodeCompleteTag(Scope *S, unsigned TagSpec) { NextRank, CurContext, Results); } - AddMacroResults(PP, NextRank, Results); + if (CodeCompleter->includeMacros()) + AddMacroResults(PP, NextRank, Results); HandleCodeCompleteResults(CodeCompleter, Results.data(), Results.size()); } @@ -1255,7 +1271,8 @@ void Sema::CodeCompleteCase(Scope *S) { } Results.ExitScope(); - AddMacroResults(PP, 1, Results); + if (CodeCompleter->includeMacros()) + AddMacroResults(PP, 1, Results); HandleCodeCompleteResults(CodeCompleter, Results.data(), Results.size()); } @@ -1350,7 +1367,8 @@ void Sema::CodeCompleteQualifiedId(Scope *S, const CXXScopeSpec &SS, if (!Results.empty() && NNS->isDependent()) Results.MaybeAddResult(CodeCompleteConsumer::Result("template", NextRank)); - AddMacroResults(PP, NextRank + 1, Results); + if (CodeCompleter->includeMacros()) + AddMacroResults(PP, NextRank + 1, Results); HandleCodeCompleteResults(CodeCompleter, Results.data(), Results.size()); } @@ -1371,7 +1389,8 @@ void Sema::CodeCompleteUsing(Scope *S) { 0, CurContext, Results); Results.ExitScope(); - AddMacroResults(PP, NextRank, Results); + if (CodeCompleter->includeMacros()) + AddMacroResults(PP, NextRank, Results); HandleCodeCompleteResults(CodeCompleter, Results.data(), Results.size()); } @@ -1386,7 +1405,8 @@ void Sema::CodeCompleteUsingDirective(Scope *S) { unsigned NextRank = CollectLookupResults(S, Context.getTranslationUnitDecl(), 0, CurContext, Results); Results.ExitScope(); - AddMacroResults(PP, NextRank, Results); + if (CodeCompleter->includeMacros()) + AddMacroResults(PP, NextRank, Results); HandleCodeCompleteResults(CodeCompleter, Results.data(), Results.size()); } @@ -1421,7 +1441,8 @@ void Sema::CodeCompleteNamespaceDecl(Scope *S) { Results.ExitScope(); } - AddMacroResults(PP, 1, Results); + if (CodeCompleter->includeMacros()) + AddMacroResults(PP, 1, Results); HandleCodeCompleteResults(CodeCompleter, Results.data(), Results.size()); } @@ -1433,7 +1454,8 @@ void Sema::CodeCompleteNamespaceAliasDecl(Scope *S) { ResultBuilder Results(*this, &ResultBuilder::IsNamespaceOrAlias); unsigned NextRank = CollectLookupResults(S, Context.getTranslationUnitDecl(), 0, CurContext, Results); - AddMacroResults(PP, NextRank, Results); + if (CodeCompleter->includeMacros()) + AddMacroResults(PP, NextRank, Results); HandleCodeCompleteResults(CodeCompleter, Results.data(), Results.size()); } @@ -1464,7 +1486,8 @@ void Sema::CodeCompleteOperatorName(Scope *S) { NextRank + 1, CurContext, Results); Results.ExitScope(); - AddMacroResults(PP, NextRank, Results); + if (CodeCompleter->includeMacros()) + AddMacroResults(PP, NextRank, Results); HandleCodeCompleteResults(CodeCompleter, Results.data(), Results.size()); } diff --git a/test/CodeCompletion/macros.c b/test/CodeCompletion/macros.c index d5c1f8f17f..82ccea2be0 100644 --- a/test/CodeCompletion/macros.c +++ b/test/CodeCompletion/macros.c @@ -13,9 +13,9 @@ struct Point { }; void test(struct Point *p) { - // RUN: clang-cc -fsyntax-only -code-completion-at=%s:17:14 %s -o - | FileCheck -check-prefix=CC1 %s && + // RUN: clang-cc -fsyntax-only -code-completion-macros -code-completion-at=%s:17:14 %s -o - | FileCheck -check-prefix=CC1 %s && switch (p->IDENTITY(color)) { - // RUN: clang-cc -fsyntax-only -code-completion-at=%s:19:9 %s -o - | FileCheck -check-prefix=CC2 %s && + // RUN: clang-cc -fsyntax-only -code-completion-macros -code-completion-at=%s:19:9 %s -o - | FileCheck -check-prefix=CC2 %s && case } // CC1: color diff --git a/tools/CIndex/CIndex.cpp b/tools/CIndex/CIndex.cpp index 4798e28328..9389139116 100644 --- a/tools/CIndex/CIndex.cpp +++ b/tools/CIndex/CIndex.cpp @@ -16,12 +16,15 @@ #include "clang/Index/Indexer.h" #include "clang/Index/ASTLocation.h" #include "clang/Index/Utils.h" +#include "clang/Sema/CodeCompleteConsumer.h" #include "clang/AST/DeclVisitor.h" #include "clang/AST/StmtVisitor.h" #include "clang/AST/Decl.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceManager.h" #include "clang/Frontend/ASTUnit.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringSwitch.h" #include "llvm/Config/config.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/MemoryBuffer.h" @@ -31,6 +34,7 @@ #include <cstdio> #include <vector> +#include <sstream> #ifdef LLVM_ON_WIN32 #define WIN32_LEAN_AND_MEAN @@ -993,5 +997,316 @@ void clang_getDefinitionSpellingAndExtent(CXCursor C, *endColumn = SM.getSpellingColumnNumber(Body->getRBracLoc()); } +enum CXCompletionChunkKind +clang_getCompletionChunkKind(CXCompletionString completion_string, + unsigned chunk_number) { + CodeCompletionString *CCStr = (CodeCompletionString *)completion_string; + if (!CCStr || chunk_number >= CCStr->size()) + return CXCompletionChunk_Text; + + switch ((*CCStr)[chunk_number].Kind) { + case CodeCompletionString::CK_TypedText: + return CXCompletionChunk_TypedText; + case CodeCompletionString::CK_Text: + return CXCompletionChunk_Text; + case CodeCompletionString::CK_Optional: + return CXCompletionChunk_Optional; + case CodeCompletionString::CK_Placeholder: + return CXCompletionChunk_Placeholder; + case CodeCompletionString::CK_Informative: + return CXCompletionChunk_Informative; + case CodeCompletionString::CK_CurrentParameter: + return CXCompletionChunk_CurrentParameter; + case CodeCompletionString::CK_LeftParen: + return CXCompletionChunk_LeftParen; + case CodeCompletionString::CK_RightParen: + return CXCompletionChunk_RightParen; + case CodeCompletionString::CK_LeftBracket: + return CXCompletionChunk_LeftBracket; + case CodeCompletionString::CK_RightBracket: + return CXCompletionChunk_RightBracket; + case CodeCompletionString::CK_LeftBrace: + return CXCompletionChunk_LeftBrace; + case CodeCompletionString::CK_RightBrace: + return CXCompletionChunk_RightBrace; + case CodeCompletionString::CK_LeftAngle: + return CXCompletionChunk_LeftAngle; + case CodeCompletionString::CK_RightAngle: + return CXCompletionChunk_RightAngle; + case CodeCompletionString::CK_Comma: + return CXCompletionChunk_Comma; + } + + // Should be unreachable, but let's be careful. + return CXCompletionChunk_Text; +} + +const char *clang_getCompletionChunkText(CXCompletionString completion_string, + unsigned chunk_number) { + CodeCompletionString *CCStr = (CodeCompletionString *)completion_string; + if (!CCStr || chunk_number >= CCStr->size()) + return 0; + + switch ((*CCStr)[chunk_number].Kind) { + case CodeCompletionString::CK_TypedText: + case CodeCompletionString::CK_Text: + case CodeCompletionString::CK_Placeholder: + case CodeCompletionString::CK_CurrentParameter: + case CodeCompletionString::CK_Informative: + case CodeCompletionString::CK_LeftParen: + case CodeCompletionString::CK_RightParen: + case CodeCompletionString::CK_LeftBracket: + case CodeCompletionString::CK_RightBracket: + case CodeCompletionString::CK_LeftBrace: + case CodeCompletionString::CK_RightBrace: + case CodeCompletionString::CK_LeftAngle: + case CodeCompletionString::CK_RightAngle: + case CodeCompletionString::CK_Comma: + return (*CCStr)[chunk_number].Text; + + case CodeCompletionString::CK_Optional: + // Note: treated as an empty text block. + return 0; + } + + // Should be unreachable, but let's be careful. + return 0; +} + +CXCompletionString +clang_getCompletionChunkCompletionString(CXCompletionString completion_string, + unsigned chunk_number) { + CodeCompletionString *CCStr = (CodeCompletionString *)completion_string; + if (!CCStr || chunk_number >= CCStr->size()) + return 0; + + switch ((*CCStr)[chunk_number].Kind) { + case CodeCompletionString::CK_TypedText: + case CodeCompletionString::CK_Text: + case CodeCompletionString::CK_Placeholder: + case CodeCompletionString::CK_CurrentParameter: + case CodeCompletionString::CK_Informative: + case CodeCompletionString::CK_LeftParen: + case CodeCompletionString::CK_RightParen: + case CodeCompletionString::CK_LeftBracket: + case CodeCompletionString::CK_RightBracket: + case CodeCompletionString::CK_LeftBrace: + case CodeCompletionString::CK_RightBrace: + case CodeCompletionString::CK_LeftAngle: + case CodeCompletionString::CK_RightAngle: + case CodeCompletionString::CK_Comma: + return 0; + + case CodeCompletionString::CK_Optional: + // Note: treated as an empty text block. + return (*CCStr)[chunk_number].Optional; + } + + // Should be unreachable, but let's be careful. + return 0; +} + +unsigned clang_getNumCompletionChunks(CXCompletionString completion_string) { + CodeCompletionString *CCStr = (CodeCompletionString *)completion_string; + return CCStr? CCStr->size() : 0; +} +static CXCursorKind parseResultKind(llvm::StringRef Str) { + return llvm::StringSwitch<CXCursorKind>(Str) + .Case("Typedef", CXCursor_TypedefDecl) + .Case("Struct", CXCursor_StructDecl) + .Case("Union", CXCursor_UnionDecl) + .Case("Class", CXCursor_ClassDecl) + .Case("Field", CXCursor_FieldDecl) + .Case("EnumConstant", CXCursor_EnumConstantDecl) + .Case("Function", CXCursor_FunctionDecl) + // FIXME: Hacks here to make C++ member functions look like C functions + .Case("CXXMethod", CXCursor_FunctionDecl) + .Case("CXXConstructor", CXCursor_FunctionDecl) + .Case("CXXDestructor", CXCursor_FunctionDecl) + .Case("CXXConversion", CXCursor_FunctionDecl) + .Case("Var", CXCursor_VarDecl) + .Case("ParmVar", CXCursor_ParmDecl) + .Case("ObjCInterface", CXCursor_ObjCInterfaceDecl) + .Case("ObjCCategory", CXCursor_ObjCCategoryDecl) + .Case("ObjCProtocol", CXCursor_ObjCProtocolDecl) + .Case("ObjCProperty", CXCursor_ObjCPropertyDecl) + .Case("ObjCIvar", CXCursor_ObjCIvarDecl) + .Case("ObjCInstanceMethod", CXCursor_ObjCInstanceMethodDecl) + .Case("ObjCClassMethod", CXCursor_ObjCClassMethodDecl) + .Default(CXCursor_NotImplemented); +} + +void clang_codeComplete(CXIndex CIdx, + const char *source_filename, + int num_command_line_args, + const char **command_line_args, + const char *complete_filename, + unsigned complete_line, + unsigned complete_column, + CXCompletionIterator completion_iterator, + CXClientData client_data) { + // The indexer, which is mainly used to determine where diagnostics go. + CIndexer *CXXIdx = static_cast<CIndexer *>(CIdx); + + // Build up the arguments for invoking 'clang'. + std::vector<const char *> argv; + + // First add the complete path to the 'clang' executable. + llvm::sys::Path ClangPath = CXXIdx->getClangPath(); + argv.push_back(ClangPath.c_str()); + + // Add the '-fsyntax-only' argument so that we only perform a basic + // syntax check of the code. + argv.push_back("-fsyntax-only"); + + // Add the appropriate '-code-completion-at=file:line:column' argument + // to perform code completion, with an "-Xclang" preceding it. + std::string code_complete_at; + code_complete_at += "-code-completion-at="; + code_complete_at += complete_filename; + code_complete_at += ":"; + code_complete_at += llvm::utostr(complete_line); + code_complete_at += ":"; + code_complete_at += llvm::utostr(complete_column); + argv.push_back("-Xclang"); + argv.push_back(code_complete_at.c_str()); + argv.push_back("-Xclang"); + argv.push_back("-code-completion-printer=cindex"); + + // Add the source file name (FIXME: later, we'll want to build temporary + // file from the buffer, or just feed the source text via standard input). + argv.push_back(source_filename); + + // Process the compiler options, stripping off '-o', '-c', '-fsyntax-only'. + for (int i = 0; i < num_command_line_args; ++i) + if (const char *arg = command_line_args[i]) { + if (strcmp(arg, "-o") == 0) { + ++i; // Also skip the matching argument. + continue; + } + if (strcmp(arg, "-emit-ast") == 0 || + strcmp(arg, "-c") == 0 || + strcmp(arg, "-fsyntax-only") == 0) { + continue; + } + + // Keep the argument. + argv.push_back(arg); + } + + // Add the null terminator. + argv.push_back(NULL); + + // Generate a temporary name for the AST file. + char tmpFile[L_tmpnam]; + char *tmpFileName = tmpnam(tmpFile); + llvm::sys::Path ResultsFile(tmpFileName); + + // Invoke 'clang'. + llvm::sys::Path DevNull; // leave empty, causes redirection to /dev/null + // on Unix or NUL (Windows). + std::string ErrMsg; + const llvm::sys::Path *Redirects[] = { &DevNull, &ResultsFile, &DevNull, 0 }; + llvm::sys::Program::ExecuteAndWait(ClangPath, &argv[0], /* env */ NULL, + /* redirects */ &Redirects[0], + /* secondsToWait */ 0, + /* memoryLimits */ 0, &ErrMsg); + + if (!ErrMsg.empty()) { + llvm::errs() << "clang_codeComplete: " << ErrMsg + << '\n' << "Arguments: \n"; + for (std::vector<const char*>::iterator I = argv.begin(), E = argv.end(); + I!=E; ++I) { + if (*I) + llvm::errs() << ' ' << *I << '\n'; + } + llvm::errs() << '\n'; + } + + // Parse the resulting source file to find code-completion results. + using llvm::MemoryBuffer; + using llvm::StringRef; + if (MemoryBuffer *F = MemoryBuffer::getFile(ResultsFile.c_str())) { + StringRef Buffer = F->getBuffer(); + do { + StringRef::size_type CompletionIdx = Buffer.find("COMPLETION:"); + StringRef::size_type OverloadIdx = Buffer.find("OVERLOAD:"); + if (CompletionIdx == StringRef::npos && OverloadIdx == StringRef::npos) + break; + + if (OverloadIdx < CompletionIdx) { + // Parse an overload result. + Buffer = Buffer.substr(OverloadIdx); + + // Skip past the OVERLOAD: + Buffer = Buffer.substr(Buffer.find(':') + 1); + + // Find the entire completion string. + StringRef::size_type EOL = Buffer.find_first_of("\n\r"); + if (EOL == StringRef::npos) + continue; + + StringRef Line = Buffer.substr(0, EOL); + Buffer = Buffer.substr(EOL + 1); + CodeCompletionString *CCStr = CodeCompletionString::Deserialize(Line); + if (!CCStr || CCStr->empty()) + continue; + + // Vend the code-completion result to the caller. + CXCompletionResult Result; + Result.CursorKind = CXCursor_NotImplemented; + Result.CompletionString = CCStr; + if (completion_iterator) + completion_iterator(&Result, client_data); + delete CCStr; + + continue; + } + + // Parse a completion result. + Buffer = Buffer.substr(CompletionIdx); + + // Skip past the COMPLETION: + Buffer = Buffer.substr(Buffer.find(':') + 1); + + // Get the rank + unsigned Rank = 0; + StringRef::size_type AfterRank = Buffer.find(':'); + Buffer.substr(0, AfterRank).getAsInteger(10, Rank); + Buffer = Buffer.substr(AfterRank + 1); + + // Get the kind of result. + StringRef::size_type AfterKind = Buffer.find(':'); + StringRef Kind = Buffer.substr(0, AfterKind); + Buffer = Buffer.substr(AfterKind + 1); + + // Skip over any whitespace. + Buffer = Buffer.substr(Buffer.find_first_not_of(" \t")); + + // Find the entire completion string. + StringRef::size_type EOL = Buffer.find_first_of("\n\r"); + if (EOL == StringRef::npos) + continue; + + StringRef Line = Buffer.substr(0, EOL); + Buffer = Buffer.substr(EOL + 1); + CodeCompletionString *CCStr = CodeCompletionString::Deserialize(Line); + if (!CCStr || CCStr->empty()) + continue; + + // Vend the code-completion result to the caller. + CXCompletionResult Result; + Result.CursorKind = parseResultKind(Kind); + Result.CompletionString = CCStr; + if (completion_iterator) + completion_iterator(&Result, client_data); + delete CCStr; + } while (true); + delete F; + } + + ResultsFile.eraseFromDisk(); +} + } // end extern "C" diff --git a/tools/CIndex/CIndex.exports b/tools/CIndex/CIndex.exports index 5f461d8ef6..ba977c1c85 100644 --- a/tools/CIndex/CIndex.exports +++ b/tools/CIndex/CIndex.exports @@ -1,8 +1,11 @@ +_clang_codeComplete _clang_createIndex _clang_createTranslationUnit _clang_createTranslationUnitFromSourceFile _clang_disposeIndex _clang_disposeTranslationUnit +_clang_getCompletionChunkKind +_clang_getCompletionChunkText _clang_getCursor _clang_getCursorColumn _clang_getCursorDecl @@ -24,6 +27,7 @@ _clang_getEntity _clang_getEntityFromDecl _clang_getFileName _clang_getFileTime +_clang_getNumCompletionChunks _clang_getTranslationUnitSpelling _clang_getURI _clang_isDeclaration diff --git a/tools/c-index-test/c-index-test.c b/tools/c-index-test/c-index-test.c index 8791ee20a5..429c5e52dc 100644 --- a/tools/c-index-test/c-index-test.c +++ b/tools/c-index-test/c-index-test.c @@ -1,6 +1,7 @@ /* c-index-test.c */ #include "clang-c/Index.h" +#include <stdlib.h> #include <stdio.h> #include <string.h> @@ -94,10 +95,115 @@ static void TranslationUnitVisitor(CXTranslationUnit Unit, CXCursor Cursor, } } +/* Parse file:line:column from the input string. Returns 0 on success, non-zero + on failure. If successful, the pointer *filename will contain newly-allocated + memory (that will be owned by the caller) to store the file name. */ +int parse_file_line_column(const char *input, char **filename, unsigned *line, + unsigned *column) { + const char *colon = strchr(input, ':'); + char *endptr = 0; + if (!colon) { + fprintf(stderr, "could not parse filename:line:column in '%s'\n", input); + return 1; + } + + /* Copy the file name. */ + *filename = (char*)malloc(colon - input); + strncpy(*filename, input, colon - input); + (*filename)[colon - input] = 0; + input = colon + 1; + + /* Parse the line number. */ + *line = strtol(input, &endptr, 10); + if (*endptr != ':') { + fprintf(stderr, "could not parse line:column in '%s'\n", input); + free(filename); + *filename = 0; + return 1; + } + input = endptr + 1; + + /* Parse the column number. */ + *column = strtol(input, &endptr, 10); + if (*endptr != 0) { + fprintf(stderr, "could not parse column in '%s'\n", input); + free(filename); + *filename = 0; + return 1; + } + + return 0; +} + +const char * +clang_getCompletionChunkKindSpelling(enum CXCompletionChunkKind Kind) { + switch (Kind) { + case CXCompletionChunk_Optional: return "Optional"; + case CXCompletionChunk_TypedText: return "TypedText"; + case CXCompletionChunk_Text: return "Text"; + case CXCompletionChunk_Placeholder: return "Placeholder"; + case CXCompletionChunk_Informative: return "Informative"; + case CXCompletionChunk_CurrentParameter: return "CurrentParameter"; + case CXCompletionChunk_LeftParen: return "LeftParen"; + case CXCompletionChunk_RightParen: return "RightParen"; + case CXCompletionChunk_LeftBracket: return "LeftBracket"; + case CXCompletionChunk_RightBracket: return "RightBracket"; + case CXCompletionChunk_LeftBrace: return "LeftBrace"; + case CXCompletionChunk_RightBrace: return "RightBrace"; + case CXCompletionChunk_LeftAngle: return "LeftAngle"; + case CXCompletionChunk_RightAngle: return "RightAngle"; + case CXCompletionChunk_Comma: return "Comma"; + } + + return "Unknown"; +} + +void print_completion_result(CXCompletionResult *completion_result, + CXClientData client_data) { + FILE *file = (FILE *)client_data; + + fprintf(file, "%s:", + clang_getCursorKindSpelling(completion_result->CursorKind)); + int I, N = clang_getNumCompletionChunks(completion_result->CompletionString); + for (I = 0; I != N; ++I) { + const char *text + = clang_getCompletionChunkText(completion_result->CompletionString, I); + + enum CXCompletionChunkKind Kind + = clang_getCompletionChunkKind(completion_result->CompletionString, I); + fprintf(file, "{%s %s}", + clang_getCompletionChunkKindSpelling(Kind), + text? text : ""); + } + fprintf(file, "\n"); +} + +void perform_code_completion(int argc, const char **argv) { + const char *input = argv[1]; + char *filename = 0; + unsigned line; + unsigned column; + input += strlen("-code-completion-at="); + if (parse_file_line_column(input, &filename, &line, &column)) + return; + + CXIndex CIdx = clang_createIndex(0, 0); + clang_codeComplete(CIdx, argv[argc - 1], argc - 3, argv + 2, + filename, line, column, &print_completion_result, stdout); + clang_disposeIndex(CIdx); + free(filename); +} + /* * First sign of life:-) */ int main(int argc, char **argv) { + if (argc > 2 && strstr(argv[1], "-code-completion-at=") == argv[1]) { + perform_code_completion(argc, (const char **)argv); + return 0; + } + + if (argc != 3) { printf("Incorrect usage of c-index-test (requires 3 arguments)\n"); return 0; diff --git a/tools/clang-cc/clang-cc.cpp b/tools/clang-cc/clang-cc.cpp index 97dc3fd6ee..cfef4bf72c 100644 --- a/tools/clang-cc/clang-cc.cpp +++ b/tools/clang-cc/clang-cc.cpp @@ -215,15 +215,45 @@ OutputFile("o", llvm::cl::desc("Specify output file")); +enum CodeCompletionPrinter { + CCP_Debug, + CCP_CIndex +}; + static llvm::cl::opt<ParsedSourceLocation> CodeCompletionAt("code-completion-at", llvm::cl::value_desc("file:line:column"), llvm::cl::desc("Dump code-completion information at a location")); +static llvm::cl::opt<CodeCompletionPrinter> +CodeCompletionPrinter("code-completion-printer", + llvm::cl::desc("Choose output type:"), + llvm::cl::init(CCP_Debug), + llvm::cl::values( + clEnumValN(CCP_Debug, "debug", + "Debug code-completion results"), + clEnumValN(CCP_CIndex, "cindex", + "Code-completion results for the CIndex library"), + clEnumValEnd)); + +static llvm::cl::opt<bool> +CodeCompletionWantsMacros("code-completion-macros", + llvm::cl::desc("Include macros in code-completion results")); + /// \brief Buld a new code-completion consumer that prints the results of /// code completion to standard output. static CodeCompleteConsumer *BuildPrintingCodeCompleter(Sema &S, void *) { - return new PrintingCodeCompleteConsumer(S, llvm::outs()); + switch (CodeCompletionPrinter.getValue()) { + case CCP_Debug: + return new PrintingCodeCompleteConsumer(S, CodeCompletionWantsMacros, + llvm::outs()); + + case CCP_CIndex: + return new CIndexCodeCompleteConsumer(S, CodeCompletionWantsMacros, + llvm::outs()); + }; + + return 0; } //===----------------------------------------------------------------------===// |