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 @@ -452,18 +452,52 @@ std::string summarizeSnippet() const { if (IsUsingDeclaration) return ""; - // Suppress function argument snippets if args are already present. - if ((Completion.Kind == CompletionItemKind::Function || - Completion.Kind == CompletionItemKind::Method || - Completion.Kind == CompletionItemKind::Constructor) && - NextTokenKind == tok::l_paren) - return ""; auto *Snippet = onlyValue<&BundledEntry::SnippetSuffix>(); if (!Snippet) // All bundles are function calls. // FIXME(ibiryukov): sometimes add template arguments to a snippet, e.g. // we need to complete 'forward<$1>($0)'. return "($0)"; + // Suppress function argument snippets cursor is followed by left + // parenthesis (and potentially arguments) or if there are potentially + // template arguments. There are cases where it would be wrong (e.g. next + // '<' token is a comparison rather than template argument list start) but + // it is less common and suppressing snippet provides better UX. + if (Completion.Kind == CompletionItemKind::Function || + Completion.Kind == CompletionItemKind::Method || + Completion.Kind == CompletionItemKind::Constructor) { + // If there is a potential template argument list, drop snippet and just + // complete symbol name. Ideally, this could generate an edit that would + // paste function arguments after template argument list but it would be + // complicated. Example: + // + // fu^ -> function + if (NextTokenKind == tok::less && Snippet->front() == '<') + return ""; + // Potentially followed by argument list. + if (NextTokenKind == tok::l_paren) { + // If snippet contains template arguments we will emit them and drop + // function arguments. Example: + // + // fu^(42) -> function(42); + if (Snippet->front() == '<') { + // Find matching '>'. Snippet->find('>') will not work in cases like + // template >. Hence, iterate through + // the snippet until the angle bracket balance reaches zero. + int Balance = 0; + size_t I = 0; + do { + if (Snippet->at(I) == '>') + --Balance; + else if (Snippet->at(I) == '<') + ++Balance; + ++I; + } while (Balance > 0); + return Snippet->substr(0, I); + } + return ""; + } + } if (EnableFunctionArgSnippets) return *Snippet; 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 @@ -3114,10 +3114,13 @@ Contains(AllOf(Labeled("Container(int Size)"), SnippetSuffix("<${1:typename T}>(${2:int Size})"), Kind(CompletionItemKind::Constructor)))); - // FIXME(kirillbobyrev): It would be nice to still produce the template - // snippet part: in this case it should be "<${1:typename T}>". EXPECT_THAT( completions(Context + "Container c = Cont^()", {}, Opts).Completions, + Contains(AllOf(Labeled("Container(int Size)"), + SnippetSuffix("<${1:typename T}>"), + Kind(CompletionItemKind::Constructor)))); + EXPECT_THAT( + completions(Context + "Container c = Cont^()", {}, Opts).Completions, Contains(AllOf(Labeled("Container(int Size)"), SnippetSuffix(""), Kind(CompletionItemKind::Constructor))));