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
diff --git a/tools/CIndex/CIndex.cpp b/tools/CIndex/CIndex.cpp
index 4798e28..9389139 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 @@
*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 5f461d8..ba977c1 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_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 8791ee2..429c5e5 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 @@
}
}
+/* 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 97dc3fd..cfef4bf 100644
--- a/tools/clang-cc/clang-cc.cpp
+++ b/tools/clang-cc/clang-cc.cpp
@@ -215,15 +215,45 @@
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;
}
//===----------------------------------------------------------------------===//