From 8e984da800594aefdeaf33da2f75cb66782f328e Mon Sep 17 00:00:00 2001 From: Douglas Gregor Date: Wed, 4 Aug 2010 16:47:14 +0000 Subject: [PATCH] Add code-completion support directly to ASTUnit, which performs code completion within the translation unit using the same command-line arguments for parsing the translation unit. Eventually, we'll reuse the precompiled preamble to improve code-completion performance, and this also gives us a place to cache results. Expose this function via the new libclang function clang_codeCompleteAt(), which performs the code completion within a CXTranslationUnit. The completion occurs in-process (clang_codeCompletion() runs code completion out-of-process). llvm-svn: 110210 --- clang/include/clang-c/Index.h | 72 +++++++- clang/include/clang/Frontend/ASTUnit.h | 19 ++ .../include/clang/Sema/CodeCompleteConsumer.h | 14 +- clang/lib/Frontend/ASTUnit.cpp | 118 +++++++++++-- clang/lib/Frontend/CompilerInstance.cpp | 49 ++++-- clang/lib/Sema/CodeCompleteConsumer.cpp | 100 +++++------ clang/lib/Sema/SemaCodeComplete.cpp | 8 +- clang/test/Index/complete-exprs.c | 2 + clang/tools/c-index-test/c-index-test.c | 20 ++- clang/tools/libclang/CIndex.cpp | 2 +- clang/tools/libclang/CIndexCodeCompletion.cpp | 165 +++++++++++++++++- clang/tools/libclang/libclang.darwin.exports | 1 + clang/tools/libclang/libclang.exports | 1 + 13 files changed, 470 insertions(+), 101 deletions(-) diff --git a/clang/include/clang-c/Index.h b/clang/include/clang-c/Index.h index 49a11612be5a..9e8623a3d871 100644 --- a/clang/include/clang-c/Index.h +++ b/clang/include/clang-c/Index.h @@ -715,7 +715,7 @@ enum CXTranslationUnit_Flags { * \p command_line_args. * * \param unsaved_files the files that have not yet been saved to disk - * but may be required for code completion, including the contents of + * but may be required for parsing, including the contents of * those files. The contents and name of these files (as specified by * CXUnsavedFile) are copied when necessary, so the client only needs to * guarantee their validity until the call to this function returns. @@ -2078,6 +2078,76 @@ CXCodeCompleteResults *clang_codeComplete(CXIndex CIdx, unsigned complete_line, unsigned complete_column); +/** + * \brief Perform code completion at a given location in a translation unit. + * + * 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 + * returned via a new \c CXCodeCompleteResults structure. + * + * 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-completion + * results from the filtering of results on a per-character basis, which must + * have a lower latency. + * + * \param TU The translation unit in which code-completion should + * occur. The source files for this translation unit need not be + * completely up-to-date (and the contents of those source files may + * be overridden via \p unsaved_files). Cursors referring into the + * translation unit may be invalidated by this invocation. + * + * \param complete_filename The name of the source file where code + * completion should be performed. This filename may be any file + * included in the translation unit. + * + * \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 unsaved_files the Tiles that have not yet been saved to disk + * but may be required for parsing or code completion, including the + * contents of those files. The contents and name of these files (as + * specified by CXUnsavedFile) are copied when necessary, so the + * client only needs to guarantee their validity until the call to + * this function returns. + * + * \param num_unsaved_files The number of unsaved file entries in \p + * unsaved_files. + * + * \returns If successful, a new \c CXCodeCompleteResults structure + * containing code-completion results, which should eventually be + * freed with \c clang_disposeCodeCompleteResults(). If code + * completion fails, returns NULL. + */ +CINDEX_LINKAGE +CXCodeCompleteResults *clang_codeCompleteAt(CXTranslationUnit TU, + const char *complete_filename, + unsigned complete_line, + unsigned complete_column, + struct CXUnsavedFile *unsaved_files, + unsigned num_unsaved_files); + /** * \brief Free the given set of code-completion results. */ diff --git a/clang/include/clang/Frontend/ASTUnit.h b/clang/include/clang/Frontend/ASTUnit.h index 1fd5a0ff2cda..0068f90c90f9 100644 --- a/clang/include/clang/Frontend/ASTUnit.h +++ b/clang/include/clang/Frontend/ASTUnit.h @@ -38,6 +38,7 @@ namespace llvm { namespace clang { class ASTContext; +class CodeCompleteConsumer; class CompilerInvocation; class Decl; class Diagnostic; @@ -384,6 +385,24 @@ public: /// contain any translation-unit information, false otherwise. bool Reparse(RemappedFile *RemappedFiles = 0, unsigned NumRemappedFiles = 0); + + /// \brief Perform code completion at the given file, line, and + /// column within this translation unit. + /// + /// \brief File The file in which code completion will occur. + /// \brief Line The line at which code completion will occur. + /// \brief Column The column at which code completion will occur. + /// \brief Consumer The consumer that will receive code-completion results. + /// + /// FIXME: The Diag, LangOpts, SourceMgr, FileMgr, and + /// StoredDiagnostics parameters are all disgusting hacks. They will + /// go away. + void CodeComplete(llvm::StringRef File, unsigned Line, unsigned Column, + RemappedFile *RemappedFiles, unsigned NumRemappedFiles, + CodeCompleteConsumer &Consumer, + Diagnostic &Diag, LangOptions &LangOpts, + SourceManager &SourceMgr, FileManager &FileMgr, + llvm::SmallVectorImpl &StoredDiagnostics); }; } // namespace clang diff --git a/clang/include/clang/Sema/CodeCompleteConsumer.h b/clang/include/clang/Sema/CodeCompleteConsumer.h index 1d9d25073177..1c679ebf8608 100644 --- a/clang/include/clang/Sema/CodeCompleteConsumer.h +++ b/clang/include/clang/Sema/CodeCompleteConsumer.h @@ -274,7 +274,10 @@ public: std::string getAsString() const; /// \brief Clone this code-completion string. - CodeCompletionString *Clone() const; + /// + /// \param Result If non-NULL, points to an empty code-completion + /// result that will be given a cloned copy of + CodeCompletionString *Clone(CodeCompletionString *Result = 0) const; /// \brief Serialize this code-completion string to the given stream. void Serialize(llvm::raw_ostream &OS) const; @@ -410,7 +413,14 @@ public: /// \brief Create a new code-completion string that describes how to insert /// this result into a program. - CodeCompletionString *CreateCodeCompletionString(Sema &S); + /// + /// \param S The semantic analysis that created the result. + /// + /// \param Result If non-NULL, the already-allocated, empty + /// code-completion string that will be populated with the + /// appropriate code completion string for this result. + CodeCompletionString *CreateCodeCompletionString(Sema &S, + CodeCompletionString *Result = 0); void Destroy(); diff --git a/clang/lib/Frontend/ASTUnit.cpp b/clang/lib/Frontend/ASTUnit.cpp index b553d7cd3b9b..5bffbacaf691 100644 --- a/clang/lib/Frontend/ASTUnit.cpp +++ b/clang/lib/Frontend/ASTUnit.cpp @@ -417,8 +417,12 @@ bool ASTUnit::Parse(llvm::MemoryBuffer *OverrideMainBuffer) { Clang.setInvocation(Invocation.take()); OriginalSourceFile = Clang.getFrontendOpts().Inputs[0].second; - // Set up diagnostics. + // Set up diagnostics, capturing any diagnostics that would + // otherwise be dropped. Clang.setDiagnostics(&getDiagnostics()); + CaptureDroppedDiagnostics Capture(CaptureDiagnostics, + getDiagnostics(), + StoredDiagnostics); Clang.setDiagnosticClient(getDiagnostics().getClient()); // Create the target instance. @@ -456,12 +460,7 @@ bool ASTUnit::Parse(llvm::MemoryBuffer *OverrideMainBuffer) { if (!OverrideMainBuffer) StoredDiagnostics.clear(); - - // Capture any diagnostics that would otherwise be dropped. - CaptureDroppedDiagnostics Capture(CaptureDiagnostics, - Clang.getDiagnostics(), - StoredDiagnostics); - + // Create a file manager object to provide access to and cache the filesystem. Clang.setFileManager(&getFileManager()); @@ -471,11 +470,13 @@ bool ASTUnit::Parse(llvm::MemoryBuffer *OverrideMainBuffer) { // If the main file has been overridden due to the use of a preamble, // make that override happen and introduce the preamble. PreprocessorOptions &PreprocessorOpts = Clang.getPreprocessorOpts(); + std::string PriorImplicitPCHInclude; if (OverrideMainBuffer) { PreprocessorOpts.addRemappedFile(OriginalSourceFile, OverrideMainBuffer); PreprocessorOpts.PrecompiledPreambleBytes.first = Preamble.size(); PreprocessorOpts.PrecompiledPreambleBytes.second = PreambleEndsAtStartOfLine; + PriorImplicitPCHInclude = PreprocessorOpts.ImplicitPCHInclude; PreprocessorOpts.ImplicitPCHInclude = PreambleFile; PreprocessorOpts.DisablePCHValidation = true; @@ -513,10 +514,12 @@ bool ASTUnit::Parse(llvm::MemoryBuffer *OverrideMainBuffer) { Act->EndSourceFile(); // Remove the overridden buffer we used for the preamble. - if (OverrideMainBuffer) + if (OverrideMainBuffer) { PreprocessorOpts.eraseRemappedFile( PreprocessorOpts.remapped_file_buffer_end() - 1); - + PreprocessorOpts.ImplicitPCHInclude = PriorImplicitPCHInclude; + } + Clang.takeDiagnosticClient(); Invocation.reset(Clang.takeInvocation()); @@ -528,6 +531,7 @@ error: PreprocessorOpts.eraseRemappedFile( PreprocessorOpts.remapped_file_buffer_end() - 1); PreprocessorOpts.DisablePCHValidation = true; + PreprocessorOpts.ImplicitPCHInclude = PriorImplicitPCHInclude; } Clang.takeSourceManager(); @@ -853,8 +857,11 @@ llvm::MemoryBuffer *ASTUnit::BuildPrecompiledPreamble() { Clang.setInvocation(&PreambleInvocation); OriginalSourceFile = Clang.getFrontendOpts().Inputs[0].second; - // Set up diagnostics. + // Set up diagnostics, capturing all of the diagnostics produced. Clang.setDiagnostics(&getDiagnostics()); + CaptureDroppedDiagnostics Capture(CaptureDiagnostics, + getDiagnostics(), + StoredDiagnostics); Clang.setDiagnosticClient(getDiagnostics().getClient()); // Create the target instance. @@ -889,11 +896,6 @@ llvm::MemoryBuffer *ASTUnit::BuildPrecompiledPreamble() { StoredDiagnostics.clear(); TopLevelDecls.clear(); TopLevelDeclsInPreamble.clear(); - - // Capture any diagnostics that would otherwise be dropped. - CaptureDroppedDiagnostics Capture(CaptureDiagnostics, - getDiagnostics(), - StoredDiagnostics); // Create a file manager object to provide access to and cache the filesystem. Clang.setFileManager(new FileManager); @@ -1127,7 +1129,6 @@ bool ASTUnit::Reparse(RemappedFile *RemappedFiles, unsigned NumRemappedFiles) { } // Remap files. - // FIXME: Do we want to remove old mappings for these files? Invocation->getPreprocessorOpts().clearRemappedFiles(); for (unsigned I = 0; I != NumRemappedFiles; ++I) Invocation->getPreprocessorOpts().addRemappedFile(RemappedFiles[I].first, @@ -1149,3 +1150,88 @@ bool ASTUnit::Reparse(RemappedFile *RemappedFiles, unsigned NumRemappedFiles) { ReparsingTimer->stopTimer(); return Result; } + +void ASTUnit::CodeComplete(llvm::StringRef File, unsigned Line, unsigned Column, + RemappedFile *RemappedFiles, + unsigned NumRemappedFiles, + CodeCompleteConsumer &Consumer, + Diagnostic &Diag, LangOptions &LangOpts, + SourceManager &SourceMgr, FileManager &FileMgr, + llvm::SmallVectorImpl &StoredDiagnostics) { + if (!Invocation.get()) + return; + + CompilerInvocation CCInvocation(*Invocation); + FrontendOptions &FrontendOpts = CCInvocation.getFrontendOpts(); + PreprocessorOptions &PreprocessorOpts = CCInvocation.getPreprocessorOpts(); + + FrontendOpts.ShowMacrosInCodeCompletion = 1; + FrontendOpts.ShowCodePatternsInCodeCompletion = 1; + FrontendOpts.CodeCompletionAt.FileName = File; + FrontendOpts.CodeCompletionAt.Line = Line; + FrontendOpts.CodeCompletionAt.Column = Column; + + // Set the language options appropriately. + LangOpts = CCInvocation.getLangOpts(); + + CompilerInstance Clang; + Clang.setInvocation(&CCInvocation); + OriginalSourceFile = Clang.getFrontendOpts().Inputs[0].second; + + // Set up diagnostics, capturing any diagnostics produced. + Clang.setDiagnostics(&Diag); + CaptureDroppedDiagnostics Capture(true, + Clang.getDiagnostics(), + StoredDiagnostics); + Clang.setDiagnosticClient(Diag.getClient()); + + // Create the target instance. + Clang.setTarget(TargetInfo::CreateTargetInfo(Clang.getDiagnostics(), + Clang.getTargetOpts())); + if (!Clang.hasTarget()) { + Clang.takeDiagnosticClient(); + Clang.takeInvocation(); + } + + // Inform the target of the language options. + // + // FIXME: We shouldn't need to do this, the target should be immutable once + // created. This complexity should be lifted elsewhere. + Clang.getTarget().setForcedLangOptions(Clang.getLangOpts()); + + assert(Clang.getFrontendOpts().Inputs.size() == 1 && + "Invocation must have exactly one source file!"); + assert(Clang.getFrontendOpts().Inputs[0].first != IK_AST && + "FIXME: AST inputs not yet supported here!"); + assert(Clang.getFrontendOpts().Inputs[0].first != IK_LLVM_IR && + "IR inputs not support here!"); + + + // Use the source and file managers that we were given. + Clang.setFileManager(&FileMgr); + Clang.setSourceManager(&SourceMgr); + + // Remap files. + PreprocessorOpts.clearRemappedFiles(); + for (unsigned I = 0; I != NumRemappedFiles; ++I) + PreprocessorOpts.addRemappedFile(RemappedFiles[I].first, + RemappedFiles[I].second); + + // Use the code completion consumer we were given. + Clang.setCodeCompletionConsumer(&Consumer); + + llvm::OwningPtr Act; + Act.reset(new SyntaxOnlyAction); + if (Act->BeginSourceFile(Clang, Clang.getFrontendOpts().Inputs[0].second, + Clang.getFrontendOpts().Inputs[0].first)) { + Act->Execute(); + Act->EndSourceFile(); + } + + // Steal back our resources. + Clang.takeFileManager(); + Clang.takeSourceManager(); + Clang.takeInvocation(); + Clang.takeDiagnosticClient(); + Clang.takeCodeCompletionConsumer(); +} diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index 84e51837bc80..76773e40d389 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -296,17 +296,41 @@ CompilerInstance::createPCHExternalASTSource(llvm::StringRef Path, // Code Completion +static bool EnableCodeCompletion(Preprocessor &PP, + const std::string &Filename, + unsigned Line, + unsigned Column) { + // Tell the source manager to chop off the given file at a specific + // line and column. + const FileEntry *Entry = PP.getFileManager().getFile(Filename); + if (!Entry) { + PP.getDiagnostics().Report(diag::err_fe_invalid_code_complete_file) + << Filename; + return true; + } + + // Truncate the named file at the given line/column. + PP.SetCodeCompletionPoint(Entry, Line, Column); + return false; +} + void CompilerInstance::createCodeCompletionConsumer() { const ParsedSourceLocation &Loc = getFrontendOpts().CodeCompletionAt; - CompletionConsumer.reset( - createCodeCompletionConsumer(getPreprocessor(), - Loc.FileName, Loc.Line, Loc.Column, - getFrontendOpts().DebugCodeCompletionPrinter, - getFrontendOpts().ShowMacrosInCodeCompletion, + if (!CompletionConsumer) { + CompletionConsumer.reset( + createCodeCompletionConsumer(getPreprocessor(), + Loc.FileName, Loc.Line, Loc.Column, + getFrontendOpts().DebugCodeCompletionPrinter, + getFrontendOpts().ShowMacrosInCodeCompletion, getFrontendOpts().ShowCodePatternsInCodeCompletion, - llvm::outs())); - if (!CompletionConsumer) + llvm::outs())); + if (!CompletionConsumer) + return; + } else if (EnableCodeCompletion(getPreprocessor(), Loc.FileName, + Loc.Line, Loc.Column)) { + CompletionConsumer.reset(); return; + } if (CompletionConsumer->isOutputBinary() && llvm::sys::Program::ChangeStdoutToBinary()) { @@ -328,17 +352,8 @@ CompilerInstance::createCodeCompletionConsumer(Preprocessor &PP, bool ShowMacros, bool ShowCodePatterns, llvm::raw_ostream &OS) { - // Tell the source manager to chop off the given file at a specific - // line and column. - const FileEntry *Entry = PP.getFileManager().getFile(Filename); - if (!Entry) { - PP.getDiagnostics().Report(diag::err_fe_invalid_code_complete_file) - << Filename; + if (EnableCodeCompletion(PP, Filename, Line, Column)) return 0; - } - - // Truncate the named file at the given line/column. - PP.SetCodeCompletionPoint(Entry, Line, Column); // Set up the creation routine for code-completion. if (UseDebugPrinter) diff --git a/clang/lib/Sema/CodeCompleteConsumer.cpp b/clang/lib/Sema/CodeCompleteConsumer.cpp index 03107769d7a8..62e4abbbbb3a 100644 --- a/clang/lib/Sema/CodeCompleteConsumer.cpp +++ b/clang/lib/Sema/CodeCompleteConsumer.cpp @@ -245,8 +245,10 @@ const char *CodeCompletionString::getTypedText() const { return 0; } -CodeCompletionString *CodeCompletionString::Clone() const { - CodeCompletionString *Result = new CodeCompletionString; +CodeCompletionString * +CodeCompletionString::Clone(CodeCompletionString *Result) const { + if (!Result) + Result = new CodeCompletionString; for (iterator C = begin(), CEnd = end(); C != CEnd; ++C) Result->AddChunk(C->Clone()); return Result; @@ -493,98 +495,81 @@ PrintingCodeCompleteConsumer::ProcessOverloadCandidates(Sema &SemaRef, } } -void -CIndexCodeCompleteConsumer::ProcessCodeCompleteResults(Sema &SemaRef, - Result *Results, - unsigned NumResults) { - // Print the results. - for (unsigned I = 0; I != NumResults; ++I) { - CXCursorKind Kind = CXCursor_NotImplemented; - - switch (Results[I].Kind) { +namespace clang { + // FIXME: Used externally by CIndexCodeCompletion.cpp; this code + // will move there, eventually, when the CIndexCodeCompleteConsumer + // dies. + CXCursorKind + getCursorKindForCompletionResult(const CodeCompleteConsumer::Result &R) { + typedef CodeCompleteConsumer::Result Result; + switch (R.Kind) { case Result::RK_Declaration: - switch (Results[I].Declaration->getKind()) { + switch (R.Declaration->getKind()) { case Decl::Record: case Decl::CXXRecord: case Decl::ClassTemplateSpecialization: { - RecordDecl *Record = cast(Results[I].Declaration); + RecordDecl *Record = cast(R.Declaration); if (Record->isStruct()) - Kind = CXCursor_StructDecl; + return CXCursor_StructDecl; else if (Record->isUnion()) - Kind = CXCursor_UnionDecl; + return CXCursor_UnionDecl; else - Kind = CXCursor_ClassDecl; - break; + return CXCursor_ClassDecl; } case Decl::ObjCMethod: { - ObjCMethodDecl *Method = cast(Results[I].Declaration); + ObjCMethodDecl *Method = cast(R.Declaration); if (Method->isInstanceMethod()) - Kind = CXCursor_ObjCInstanceMethodDecl; + return CXCursor_ObjCInstanceMethodDecl; else - Kind = CXCursor_ObjCClassMethodDecl; - break; + return CXCursor_ObjCClassMethodDecl; } case Decl::Typedef: - Kind = CXCursor_TypedefDecl; - break; + return CXCursor_TypedefDecl; case Decl::Enum: - Kind = CXCursor_EnumDecl; - break; + return CXCursor_EnumDecl; case Decl::Field: - Kind = CXCursor_FieldDecl; - break; + return CXCursor_FieldDecl; case Decl::EnumConstant: - Kind = CXCursor_EnumConstantDecl; - break; + return CXCursor_EnumConstantDecl; case Decl::Function: case Decl::CXXMethod: case Decl::CXXConstructor: case Decl::CXXDestructor: case Decl::CXXConversion: - Kind = CXCursor_FunctionDecl; - break; + return CXCursor_FunctionDecl; case Decl::Var: - Kind = CXCursor_VarDecl; - break; + return CXCursor_VarDecl; case Decl::ParmVar: - Kind = CXCursor_ParmDecl; - break; + return CXCursor_ParmDecl; case Decl::ObjCInterface: - Kind = CXCursor_ObjCInterfaceDecl; - break; + return CXCursor_ObjCInterfaceDecl; case Decl::ObjCCategory: - Kind = CXCursor_ObjCCategoryDecl; - break; + return CXCursor_ObjCCategoryDecl; case Decl::ObjCProtocol: - Kind = CXCursor_ObjCProtocolDecl; - break; + return CXCursor_ObjCProtocolDecl; case Decl::ObjCProperty: - Kind = CXCursor_ObjCPropertyDecl; - break; + return CXCursor_ObjCPropertyDecl; case Decl::ObjCIvar: - Kind = CXCursor_ObjCIvarDecl; - break; + return CXCursor_ObjCIvarDecl; case Decl::ObjCImplementation: - Kind = CXCursor_ObjCImplementationDecl; - break; + return CXCursor_ObjCImplementationDecl; case Decl::ObjCCategoryImpl: - Kind = CXCursor_ObjCCategoryImplDecl; - break; + return CXCursor_ObjCCategoryImplDecl; default: break; @@ -592,16 +577,23 @@ CIndexCodeCompleteConsumer::ProcessCodeCompleteResults(Sema &SemaRef, break; case Result::RK_Macro: - Kind = CXCursor_MacroDefinition; - break; + return CXCursor_MacroDefinition; case Result::RK_Keyword: case Result::RK_Pattern: - Kind = CXCursor_NotImplemented; - break; + return CXCursor_NotImplemented; } + return CXCursor_NotImplemented; + } +} - WriteUnsigned(OS, Kind); +void +CIndexCodeCompleteConsumer::ProcessCodeCompleteResults(Sema &SemaRef, + Result *Results, + unsigned NumResults) { + // Print the results. + for (unsigned I = 0; I != NumResults; ++I) { + WriteUnsigned(OS, getCursorKindForCompletionResult(Results[I])); WriteUnsigned(OS, Results[I].Priority); CodeCompletionString *CCS = Results[I].CreateCodeCompletionString(SemaRef); assert(CCS && "No code-completion string?"); diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp index 476c79cc37b7..52ac8dd64987 100644 --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -1811,13 +1811,15 @@ static void AddFunctionTypeQualsToCompletionString(CodeCompletionString *Result, /// how to use this result, or NULL to indicate that the string or name of the /// result is all that is needed. CodeCompletionString * -CodeCompleteConsumer::Result::CreateCodeCompletionString(Sema &S) { +CodeCompleteConsumer::Result::CreateCodeCompletionString(Sema &S, + CodeCompletionString *Result) { typedef CodeCompletionString::Chunk Chunk; if (Kind == RK_Pattern) - return Pattern->Clone(); + return Pattern->Clone(Result); - CodeCompletionString *Result = new CodeCompletionString; + if (!Result) + Result = new CodeCompletionString; if (Kind == RK_Keyword) { Result->AddTypedTextChunk(Keyword); diff --git a/clang/test/Index/complete-exprs.c b/clang/test/Index/complete-exprs.c index b7bed8c5fde8..ef19d1484d46 100644 --- a/clang/test/Index/complete-exprs.c +++ b/clang/test/Index/complete-exprs.c @@ -13,12 +13,14 @@ void f2() { f1(17); } const char *str = "Hello, \nWorld"; // RUN: c-index-test -code-completion-at=%s:7:9 -Xclang -code-completion-patterns %s | FileCheck -check-prefix=CHECK-CC1 %s +// RUN: env CINDEXTEST_EDITING=1 c-index-test -code-completion-at=%s:7:9 -Xclang -code-completion-patterns %s | FileCheck -check-prefix=CHECK-CC1 %s // CHECK-CC1: macro definition:{TypedText __VERSION__} (70) // CHECK-CC1: FunctionDecl:{ResultType int}{TypedText f}{LeftParen (}{Placeholder int}{RightParen )} (12) // CHECK-CC1-NOT: NotImplemented:{TypedText float} (40) // CHECK-CC1: ParmDecl:{ResultType int}{TypedText j} (2) // CHECK-CC1: NotImplemented:{TypedText sizeof}{LeftParen (}{Placeholder expression-or-type}{RightParen )} (30) // RUN: c-index-test -code-completion-at=%s:7:14 -Xclang -code-completion-patterns %s | FileCheck -check-prefix=CHECK-CC3 %s +// RUN: env CINDEXTEST_EDITING=1 c-index-test -code-completion-at=%s:7:14 -Xclang -code-completion-patterns %s | FileCheck -check-prefix=CHECK-CC3 %s // CHECK-CC3: macro definition:{TypedText __VERSION__} (70) // CHECK-CC3: FunctionDecl:{ResultType int}{TypedText f}{LeftParen (}{Placeholder int}{RightParen )} (50) // CHECK-CC3-NOT: NotImplemented:{TypedText float} (40) diff --git a/clang/tools/c-index-test/c-index-test.c b/clang/tools/c-index-test/c-index-test.c index 795c19c0cb5a..81462132251a 100644 --- a/clang/tools/c-index-test/c-index-test.c +++ b/clang/tools/c-index-test/c-index-test.c @@ -888,11 +888,21 @@ int perform_code_completion(int argc, const char **argv, int timing_only) { return -1; CIdx = clang_createIndex(0, 1); - results = clang_codeComplete(CIdx, - argv[argc - 1], argc - num_unsaved_files - 3, - argv + num_unsaved_files + 2, - num_unsaved_files, unsaved_files, - filename, line, column); + if (getenv("CINDEXTEST_EDITING")) { + CXTranslationUnit *TU = clang_parseTranslationUnit(CIdx, 0, + argv + num_unsaved_files + 2, + argc - num_unsaved_files - 2, + unsaved_files, + num_unsaved_files, + getDefaultParsingOptions()); + results = clang_codeCompleteAt(TU, filename, line, column, + unsaved_files, num_unsaved_files); + } else + results = clang_codeComplete(CIdx, + argv[argc - 1], argc - num_unsaved_files - 3, + argv + num_unsaved_files + 2, + num_unsaved_files, unsaved_files, + filename, line, column); if (results) { unsigned i, n = results->NumResults; diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp index b90740f8b0a7..6414166d3723 100644 --- a/clang/tools/libclang/CIndex.cpp +++ b/clang/tools/libclang/CIndex.cpp @@ -1443,7 +1443,7 @@ int clang_reparseTranslationUnit(CXTranslationUnit TU, for (unsigned I = 0; I != num_unsaved_files; ++I) { llvm::StringRef Data(unsaved_files[I].Contents, unsaved_files[I].Length); const llvm::MemoryBuffer *Buffer - = llvm::MemoryBuffer::getMemBufferCopy(Data, unsaved_files[I].Filename); + = llvm::MemoryBuffer::getMemBufferCopy(Data, unsaved_files[I].Filename); RemappedFiles.push_back(std::make_pair(unsaved_files[I].Filename, Buffer)); } diff --git a/clang/tools/libclang/CIndexCodeCompletion.cpp b/clang/tools/libclang/CIndexCodeCompletion.cpp index 2df5241d0225..f8fd470a1d8a 100644 --- a/clang/tools/libclang/CIndexCodeCompletion.cpp +++ b/clang/tools/libclang/CIndexCodeCompletion.cpp @@ -16,6 +16,7 @@ #include "CIndexDiagnostic.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/FileManager.h" +#include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Sema/CodeCompleteConsumer.h" @@ -231,7 +232,7 @@ struct AllocatedCXCodeCompleteResults : public CXCodeCompleteResults { llvm::SmallVector Diagnostics; /// \brief Diag object - Diagnostic Diag; + llvm::IntrusiveRefCntPtr Diag; /// \brief Language options used to adjust source locations. LangOptions LangOpts; @@ -248,7 +249,8 @@ struct AllocatedCXCodeCompleteResults : public CXCodeCompleteResults { }; AllocatedCXCodeCompleteResults::AllocatedCXCodeCompleteResults() - : CXCodeCompleteResults(), Buffer(0), SourceMgr(Diag) { } + : CXCodeCompleteResults(), Buffer(0), Diag(new Diagnostic), + SourceMgr(*Diag) { } AllocatedCXCodeCompleteResults::~AllocatedCXCodeCompleteResults() { for (unsigned I = 0, N = NumResults; I != N; ++I) @@ -454,6 +456,165 @@ CXCodeCompleteResults *clang_codeComplete(CXIndex CIdx, // destroyed. Results->TemporaryFiles.swap(TemporaryFiles); +#ifdef UDP_CODE_COMPLETION_LOGGER +#ifdef UDP_CODE_COMPLETION_LOGGER_PORT + const llvm::TimeRecord &EndTime = llvm::TimeRecord::getCurrentTime(); + llvm::SmallString<256> LogResult; + llvm::raw_svector_ostream os(LogResult); + + // Figure out the language and whether or not it uses PCH. + const char *lang = 0; + bool usesPCH = false; + + for (std::vector::iterator I = argv.begin(), E = argv.end(); + I != E; ++I) { + if (*I == 0) + continue; + if (strcmp(*I, "-x") == 0) { + if (I + 1 != E) { + lang = *(++I); + continue; + } + } + else if (strcmp(*I, "-include") == 0) { + if (I+1 != E) { + const char *arg = *(++I); + llvm::SmallString<512> pchName; + { + llvm::raw_svector_ostream os(pchName); + os << arg << ".pth"; + } + pchName.push_back('\0'); + struct stat stat_results; + if (stat(pchName.data(), &stat_results) == 0) + usesPCH = true; + continue; + } + } + } + + os << "{ "; + os << "\"wall\": " << (EndTime.getWallTime() - StartTime.getWallTime()); + os << ", \"numRes\": " << Results->NumResults; + os << ", \"diags\": " << Results->Diagnostics.size(); + os << ", \"pch\": " << (usesPCH ? "true" : "false"); + os << ", \"lang\": \"" << (lang ? lang : "") << '"'; + const char *name = getlogin(); + os << ", \"user\": \"" << (name ? name : "unknown") << '"'; + os << ", \"clangVer\": \"" << getClangFullVersion() << '"'; + os << " }"; + + llvm::StringRef res = os.str(); + if (res.size() > 0) { + do { + // Setup the UDP socket. + struct sockaddr_in servaddr; + bzero(&servaddr, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_port = htons(UDP_CODE_COMPLETION_LOGGER_PORT); + if (inet_pton(AF_INET, UDP_CODE_COMPLETION_LOGGER, + &servaddr.sin_addr) <= 0) + break; + + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sockfd < 0) + break; + + sendto(sockfd, res.data(), res.size(), 0, + (struct sockaddr *)&servaddr, sizeof(servaddr)); + close(sockfd); + } + while (false); + } +#endif +#endif + return Results; +} + +} // end extern "C" + +namespace clang { + // FIXME: defined in CodeCompleteConsumer.cpp, but should be a + // static function here. + CXCursorKind + getCursorKindForCompletionResult(const CodeCompleteConsumer::Result &R); +} + + +namespace { + class CaptureCompletionResults : public CodeCompleteConsumer { + AllocatedCXCodeCompleteResults &AllocatedResults; + + public: + explicit CaptureCompletionResults(AllocatedCXCodeCompleteResults &Results) + : CodeCompleteConsumer(true, false, false), AllocatedResults(Results) { } + + virtual void ProcessCodeCompleteResults(Sema &S, Result *Results, + unsigned NumResults) { + AllocatedResults.Results = new CXCompletionResult [NumResults]; + AllocatedResults.NumResults = NumResults; + for (unsigned I = 0; I != NumResults; ++I) { + CXStoredCodeCompletionString *StoredCompletion + = new CXStoredCodeCompletionString(Results[I].Priority); + (void)Results[I].CreateCodeCompletionString(S, StoredCompletion); + AllocatedResults.Results[I].CursorKind + = getCursorKindForCompletionResult(Results[I]); + AllocatedResults.Results[I].CompletionString = StoredCompletion; + } + } + }; +} + +extern "C" { +CXCodeCompleteResults *clang_codeCompleteAt(CXTranslationUnit TU, + const char *complete_filename, + unsigned complete_line, + unsigned complete_column, + struct CXUnsavedFile *unsaved_files, + unsigned num_unsaved_files) { +#ifdef UDP_CODE_COMPLETION_LOGGER +#ifdef UDP_CODE_COMPLETION_LOGGER_PORT + const llvm::TimeRecord &StartTime = llvm::TimeRecord::getCurrentTime(); +#endif +#endif + + bool EnableLogging = getenv("LIBCLANG_CODE_COMPLETION_LOGGING") != 0; + + ASTUnit *AST = static_cast(TU); + if (!AST) + return 0; + + // Perform the remapping of source files. + llvm::SmallVector RemappedFiles; + for (unsigned I = 0; I != num_unsaved_files; ++I) { + llvm::StringRef Data(unsaved_files[I].Contents, unsaved_files[I].Length); + const llvm::MemoryBuffer *Buffer + = llvm::MemoryBuffer::getMemBufferCopy(Data, unsaved_files[I].Filename); + RemappedFiles.push_back(std::make_pair(unsaved_files[I].Filename, + Buffer)); + } + + if (EnableLogging) { + // FIXME: Add logging. + } + + // Parse the resulting source file to find code-completion results. + AllocatedCXCodeCompleteResults *Results = new AllocatedCXCodeCompleteResults; + Results->Results = 0; + Results->NumResults = 0; + Results->Buffer = 0; + + // Create a code-completion consumer to capture the results. + CaptureCompletionResults Capture(*Results); + + // Perform completion. + AST->CodeComplete(complete_filename, complete_line, complete_column, + RemappedFiles.data(), RemappedFiles.size(), Capture, + *Results->Diag, Results->LangOpts, Results->SourceMgr, + Results->FileMgr, Results->Diagnostics); + + + #ifdef UDP_CODE_COMPLETION_LOGGER #ifdef UDP_CODE_COMPLETION_LOGGER_PORT const llvm::TimeRecord &EndTime = llvm::TimeRecord::getCurrentTime(); diff --git a/clang/tools/libclang/libclang.darwin.exports b/clang/tools/libclang/libclang.darwin.exports index 50ed994b1fed..b971ed383827 100644 --- a/clang/tools/libclang/libclang.darwin.exports +++ b/clang/tools/libclang/libclang.darwin.exports @@ -1,6 +1,7 @@ _clang_CXXMethod_isStatic _clang_annotateTokens _clang_codeComplete +_clang_codeCompleteAt _clang_codeCompleteGetDiagnostic _clang_codeCompleteGetNumDiagnostics _clang_constructUSR_ObjCCategory diff --git a/clang/tools/libclang/libclang.exports b/clang/tools/libclang/libclang.exports index 752c6504c9bd..b5533fcbcfc1 100644 --- a/clang/tools/libclang/libclang.exports +++ b/clang/tools/libclang/libclang.exports @@ -1,6 +1,7 @@ clang_CXXMethod_isStatic clang_annotateTokens clang_codeComplete +clang_codeCompleteAt clang_codeCompleteGetDiagnostic clang_codeCompleteGetNumDiagnostics clang_constructUSR_ObjCCategory