diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -48,6 +48,7 @@ #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Lex/ExternalPreprocessorSource.h" +#include "clang/Lex/Lexer.h" #include "clang/Lex/Preprocessor.h" #include "clang/Lex/PreprocessorOptions.h" #include "clang/Sema/CodeCompleteConsumer.h" @@ -1221,6 +1222,10 @@ CompletionRecorder *Recorder = nullptr; CodeCompletionContext::Kind CCContextKind = CodeCompletionContext::CCC_Other; bool IsUsingDeclaration = false; + // The snippets will not be generated if the token following completion + // location is an opening parenthesis (tok::l_paren) because this would add + // extra parenthesis. + bool HasParenthesisAfter = false; // Counters for logging. int NSema = 0, NIndex = 0, NSemaAndIndex = 0, NIdent = 0; bool Incomplete = false; // Would more be available with a higher limit? @@ -1272,6 +1277,10 @@ assert(Recorder && "Recorder is not set"); CCContextKind = Recorder->CCContext.getKind(); IsUsingDeclaration = Recorder->CCContext.isUsingDeclaration(); + const auto NextToken = Lexer::findNextToken( + Recorder->CCSema->getPreprocessor().getCodeCompletionLoc(), + Recorder->CCSema->getSourceManager(), Recorder->CCSema->LangOpts); + HasParenthesisAfter = NextToken->getKind() == tok::l_paren; auto Style = getFormatStyleForFile(SemaCCInput.FileName, SemaCCInput.ParseInput.Contents, SemaCCInput.ParseInput.FS.get()); @@ -1689,11 +1698,18 @@ CodeCompletionString *SemaCCS = Item.SemaResult ? Recorder->codeCompletionString(*Item.SemaResult) : nullptr; + // FIXME(kirillbobyrev): Instead of not generating any snippets when + // tok::l_paren is the next token after completion location, use more + // advanced techniques. For example, if the snippet is a pair of + // parenthesis with some arguments `(${1: int I}, ${2: char C})` and the + // completion location is only followed by a pair of parenthesis without + // any arguments inside (or without the sufficient number of arguments), + // replace these parenthesis with the contents of the snippet. if (!Builder) - Builder.emplace(Recorder ? &Recorder->CCSema->getASTContext() : nullptr, - Item, SemaCCS, QueryScopes, *Inserter, FileName, - CCContextKind, Opts, - /*GenerateSnippets=*/!IsUsingDeclaration); + Builder.emplace( + Recorder ? &Recorder->CCSema->getASTContext() : nullptr, Item, + SemaCCS, QueryScopes, *Inserter, FileName, CCContextKind, Opts, + /*GenerateSnippets=*/!IsUsingDeclaration && !HasParenthesisAfter); else Builder->add(Item, SemaCCS); } diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -2878,6 +2878,27 @@ } } +TEST(CompletionTest, RemoveSnippetOnContext) { + clangd::CodeCompleteOptions Opts; + Opts.EnableSnippets = true; + std::string Context = R"cpp( + int foo(int A); + int bar(); + int Variable = 42; + )cpp"; + EXPECT_THAT(completions(Context + "int y = fo^", {}, Opts).Completions, + UnorderedElementsAre( + AllOf(Labeled("foo(int A)"), SnippetSuffix("(${1:int A})")))); + EXPECT_THAT( + completions(Context + "int y = fo^(42)", {}, Opts).Completions, + UnorderedElementsAre(AllOf(Labeled("foo(int A)"), SnippetSuffix("")))); + EXPECT_THAT( + completions(Context + "int y = ba^", {}, Opts).Completions, + UnorderedElementsAre(AllOf(Labeled("bar()"), SnippetSuffix("()")))); + EXPECT_THAT(completions(Context + "int y = ba^()", {}, Opts).Completions, + UnorderedElementsAre(AllOf(Labeled("bar()"), SnippetSuffix("")))); +} + } // namespace } // namespace clangd } // namespace clang