@@ -158,12 +158,28 @@ auto extract_signature(const clang::CodeCompletionString& ccs) -> std::string {
158158 return signature;
159159}
160160
161+ // / Find the first non-whitespace character after the given offset in content.
162+ // / Returns '\0' if none found (end of content).
163+ auto next_token_char (llvm::StringRef content, std::uint32_t offset) -> char {
164+ for (auto i = offset; i < content.size (); ++i) {
165+ char c = content[i];
166+ if (c != ' ' && c != ' \t ' && c != ' \n ' && c != ' \r ' ) {
167+ return c;
168+ }
169+ }
170+ return ' \0 ' ;
171+ }
172+
161173// / Build a snippet string from a CodeCompletionString.
162174// / Produces e.g. "funcName(${1:int x}, ${2:float y})" for functions,
163175// / or "ClassName<${1:T}>" for class templates.
164- auto build_snippet (const clang::CodeCompletionString& ccs) -> std::string {
176+ // / If skip_parens is true, omits everything from '(' onward (when the next
177+ // / token after the cursor is already '(').
178+ auto build_snippet (const clang::CodeCompletionString& ccs, bool skip_parens = false )
179+ -> std::string {
165180 std::string snippet;
166181 unsigned placeholder_index = 0 ;
182+ bool in_parens = false ;
167183
168184 for (const auto & chunk: ccs) {
169185 using CK = clang::CodeCompletionString::ChunkKind;
@@ -174,33 +190,47 @@ auto build_snippet(const clang::CodeCompletionString& ccs) -> std::string {
174190 }
175191 break ;
176192 case CK::CK_Placeholder:
193+ if (in_parens && skip_parens) {
194+ break ;
195+ }
177196 if (chunk.Text ) {
178197 snippet += std::format (" ${{{0}:{1}}}" , ++placeholder_index, chunk.Text );
179198 }
180199 break ;
181- case CK::CK_LeftParen: snippet += ' (' ; break ;
182- case CK::CK_RightParen: snippet += ' )' ; break ;
200+ case CK::CK_LeftParen:
201+ in_parens = true ;
202+ if (!skip_parens) {
203+ snippet += ' (' ;
204+ }
205+ break ;
206+ case CK::CK_RightParen:
207+ in_parens = false ;
208+ if (!skip_parens) {
209+ snippet += ' )' ;
210+ }
211+ break ;
183212 case CK::CK_LeftAngle: snippet += ' <' ; break ;
184213 case CK::CK_RightAngle: snippet += ' >' ; break ;
185- case CK::CK_Comma: snippet += " , " ; break ;
214+ case CK::CK_Comma:
215+ if (!(in_parens && skip_parens)) {
216+ snippet += " , " ;
217+ }
218+ break ;
186219 case CK::CK_Text:
187- if (chunk.Text ) {
220+ if (!(in_parens && skip_parens) && chunk.Text ) {
188221 snippet += chunk.Text ;
189222 }
190223 break ;
191- case CK::CK_Optional:
192- // Optional chunks contain default arguments — skip for snippet.
193- break ;
224+ case CK::CK_Optional: break ;
194225 case CK::CK_Informative:
195226 case CK::CK_ResultType:
196- case CK::CK_CurrentParameter:
197- // Display-only chunks, not part of insertion.
198- break ;
227+ case CK::CK_CurrentParameter: break ;
199228 default : break ;
200229 }
201230 }
202231
203- // If no placeholders were generated, return empty to signal plain text.
232+ // If no placeholders were generated and parens were skipped,
233+ // return empty to signal plain text.
204234 if (placeholder_index == 0 ) {
205235 return {};
206236 }
@@ -229,9 +259,11 @@ class CodeCompletionCollector final : public clang::CodeCompleteConsumer {
229259 CodeCompletionCollector (std::uint32_t offset,
230260 PositionEncoding encoding,
231261 std::vector<protocol::CompletionItem>& output,
232- const CodeCompletionOptions& options) :
262+ const CodeCompletionOptions& options,
263+ llvm::StringRef original_content) :
233264 clang::CodeCompleteConsumer ({}), offset(offset), encoding(encoding), output(output),
234- options (options), info(std::make_shared<clang::GlobalCodeCompletionAllocator>()) {}
265+ options (options), original_content(original_content),
266+ info (std::make_shared<clang::GlobalCodeCompletionAllocator>()) {}
235267
236268 clang::CodeCompletionAllocator& getAllocator () final {
237269 return info.getAllocator ();
@@ -425,7 +457,8 @@ class CodeCompletionCollector final : public clang::CodeCompleteConsumer {
425457 // Generate snippet for non-bundled callables.
426458 if (is_callable && !options.bundle_overloads &&
427459 options.enable_function_arguments_snippet ) {
428- snippet = build_snippet (*ccs);
460+ bool next_is_paren = next_token_char (original_content, offset) == ' (' ;
461+ snippet = build_snippet (*ccs, /* skip_parens=*/ next_is_paren);
429462 }
430463 }
431464
@@ -495,6 +528,7 @@ class CodeCompletionCollector final : public clang::CodeCompleteConsumer {
495528 std::uint32_t offset;
496529 PositionEncoding encoding;
497530 std::vector<protocol::CompletionItem>& output;
531+ llvm::StringRef original_content;
498532 const CodeCompletionOptions& options;
499533 clang::CodeCompletionTUInfo info;
500534};
@@ -509,7 +543,15 @@ auto code_complete(CompilationParams& params,
509543 auto & [file, offset] = params.completion ;
510544 (void )file;
511545
512- auto * consumer = new CodeCompletionCollector (offset, encoding, items, options);
546+ // Get the original file content for lookahead (smart parens detection).
547+ llvm::StringRef original_content;
548+ auto buf_it = params.buffers .find (file);
549+ if (buf_it != params.buffers .end ()) {
550+ original_content = buf_it->second ->getBuffer ();
551+ }
552+
553+ auto * consumer =
554+ new CodeCompletionCollector (offset, encoding, items, options, original_content);
513555 auto unit = complete (params, consumer);
514556 (void )unit;
515557
0 commit comments