Skip to content

Commit bd238fe

Browse files
16bit-ykikoclaude
andauthored
feat(completion): signature display, underscore filtering, label dedup (#411)
## Summary - Extract function/method signatures from Clang `CodeCompletionString` into `labelDetails.detail` (parameter list) and `labelDetails.description` (return type) - Filter `_`/`__` prefixed internal symbols (e.g. `_Vector_base`, `_Alloc`) unless the user explicitly typed `_` - Fix `completion_kind` isa ordering: `CXXMethodDecl` checked before `FunctionDecl` so methods get correct Kind - Bundle mode: extend overload bundling to Method and Constructor (was Function only) - Bundle mode: deduplicate by label — when the same name appears as Class + Constructor + deduction guide, keep only one (priority: Class > Function > Constructor) - Bundled overloads show `(…) +N overloads` in `labelDetails.detail` instead of `detail` ## Test plan - [x] 12 unit tests covering: signature extraction, return type, overload bundling, underscore filtering, label deduplication, non-bundle mode, method signatures - [x] All 489 unit tests pass - [x] `pixi run format` applied 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Code completion now displays function signatures and return types in completion items * Overloaded functions are bundled together with a count indicator * Internal symbols (underscore-prefixed) are filtered from suggestions unless explicitly typed * Duplicate completion items are deduplicated while preserving higher-priority variants <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8b3e3a9 commit bd238fe

File tree

2 files changed

+325
-20
lines changed

2 files changed

+325
-20
lines changed

src/feature/code_completion.cpp

Lines changed: 149 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ auto completion_kind(const clang::NamedDecl* decl) -> protocol::CompletionItemKi
5353
return protocol::CompletionItemKind::Module;
5454
}
5555

56-
if(llvm::isa<clang::FunctionDecl, clang::FunctionTemplateDecl>(decl)) {
57-
return protocol::CompletionItemKind::Function;
56+
if(llvm::isa<clang::CXXConstructorDecl>(decl)) {
57+
return protocol::CompletionItemKind::Constructor;
5858
}
5959

6060
if(llvm::isa<clang::CXXMethodDecl,
@@ -64,8 +64,8 @@ auto completion_kind(const clang::NamedDecl* decl) -> protocol::CompletionItemKi
6464
return protocol::CompletionItemKind::Method;
6565
}
6666

67-
if(llvm::isa<clang::CXXConstructorDecl>(decl)) {
68-
return protocol::CompletionItemKind::Constructor;
67+
if(llvm::isa<clang::FunctionDecl, clang::FunctionTemplateDecl>(decl)) {
68+
return protocol::CompletionItemKind::Function;
6969
}
7070

7171
if(llvm::isa<clang::FieldDecl, clang::IndirectFieldDecl>(decl)) {
@@ -109,6 +109,65 @@ auto completion_kind(const clang::NamedDecl* decl) -> protocol::CompletionItemKi
109109
return protocol::CompletionItemKind::Text;
110110
}
111111

112+
/// Extract the function signature (parameter list) from a CodeCompletionString.
113+
/// Returns something like "(int x, float y)" for display in labelDetails.detail.
114+
auto extract_signature(const clang::CodeCompletionString& ccs) -> std::string {
115+
std::string signature;
116+
bool in_parens = false;
117+
118+
for(const auto& chunk: ccs) {
119+
using CK = clang::CodeCompletionString::ChunkKind;
120+
switch(chunk.Kind) {
121+
case CK::CK_LeftParen:
122+
in_parens = true;
123+
signature += '(';
124+
break;
125+
case CK::CK_RightParen:
126+
signature += ')';
127+
in_parens = false;
128+
break;
129+
case CK::CK_Placeholder:
130+
case CK::CK_CurrentParameter:
131+
if(in_parens && chunk.Text) {
132+
signature += chunk.Text;
133+
}
134+
break;
135+
case CK::CK_Text:
136+
case CK::CK_Informative:
137+
if(in_parens && chunk.Text) {
138+
signature += chunk.Text;
139+
}
140+
break;
141+
case CK::CK_LeftAngle:
142+
signature += '<';
143+
in_parens = true;
144+
break;
145+
case CK::CK_RightAngle:
146+
signature += '>';
147+
in_parens = false;
148+
break;
149+
case CK::CK_Comma:
150+
if(in_parens) {
151+
signature += ", ";
152+
}
153+
break;
154+
default: break;
155+
}
156+
}
157+
158+
return signature;
159+
}
160+
161+
/// Extract the return type from a CodeCompletionString.
162+
auto extract_return_type(const clang::CodeCompletionString& ccs) -> std::string {
163+
for(const auto& chunk: ccs) {
164+
if(chunk.Kind == clang::CodeCompletionString::CK_ResultType && chunk.Text) {
165+
return chunk.Text;
166+
}
167+
}
168+
return {};
169+
}
170+
112171
struct OverloadItem {
113172
protocol::CompletionItem item;
114173
float score = 0.0F;
@@ -159,6 +218,8 @@ class CodeCompletionCollector final : public clang::CodeCompleteConsumer {
159218
overloads.reserve(candidate_count);
160219
std::unordered_map<std::string, std::size_t> overload_index;
161220

221+
bool prefix_starts_with_underscore = prefix.spelling.starts_with("_");
222+
162223
auto build_item =
163224
[&](llvm::StringRef label, protocol::CompletionItemKind kind, llvm::StringRef insert) {
164225
protocol::CompletionItem item{
@@ -177,11 +238,18 @@ class CodeCompletionCollector final : public clang::CodeCompleteConsumer {
177238
auto try_add = [&](llvm::StringRef label,
178239
protocol::CompletionItemKind kind,
179240
llvm::StringRef insert_text,
180-
llvm::StringRef overload_key) {
241+
llvm::StringRef overload_key,
242+
llvm::StringRef signature = {},
243+
llvm::StringRef return_type = {}) {
181244
if(label.empty()) {
182245
return;
183246
}
184247

248+
// Filter out _/__ prefixed internal symbols unless user typed _.
249+
if(!prefix_starts_with_underscore && label.starts_with("_")) {
250+
return;
251+
}
252+
185253
auto score = matcher.match(label);
186254
if(!score.has_value()) {
187255
return;
@@ -193,6 +261,16 @@ class CodeCompletionCollector final : public clang::CodeCompleteConsumer {
193261
if(inserted) {
194262
auto item = build_item(label, kind, insert_text);
195263
item.sort_text = std::format("{}", *score);
264+
if(!signature.empty() || !return_type.empty()) {
265+
protocol::CompletionItemLabelDetails details;
266+
if(!signature.empty()) {
267+
details.detail = signature.str();
268+
}
269+
if(!return_type.empty()) {
270+
details.description = return_type.str();
271+
}
272+
item.label_details = std::move(details);
273+
}
196274
overloads.push_back({
197275
.item = std::move(item),
198276
.score = *score,
@@ -211,6 +289,16 @@ class CodeCompletionCollector final : public clang::CodeCompleteConsumer {
211289

212290
auto item = build_item(label, kind, insert_text);
213291
item.sort_text = std::format("{}", *score);
292+
if(!signature.empty() || !return_type.empty()) {
293+
protocol::CompletionItemLabelDetails details;
294+
if(!signature.empty()) {
295+
details.detail = signature.str();
296+
}
297+
if(!return_type.empty()) {
298+
details.description = return_type.str();
299+
}
300+
item.label_details = std::move(details);
301+
}
214302
collected.push_back(std::move(item));
215303
};
216304

@@ -246,24 +334,77 @@ class CodeCompletionCollector final : public clang::CodeCompleteConsumer {
246334
auto kind = completion_kind(declaration);
247335

248336
llvm::SmallString<256> qualified_name;
249-
if(options.bundle_overloads && kind == protocol::CompletionItemKind::Function) {
337+
bool is_callable = kind == protocol::CompletionItemKind::Function ||
338+
kind == protocol::CompletionItemKind::Method ||
339+
kind == protocol::CompletionItemKind::Constructor;
340+
if(options.bundle_overloads && is_callable) {
250341
llvm::raw_svector_ostream stream(qualified_name);
251342
declaration->printQualifiedName(stream);
252343
}
253344

254-
try_add(label, kind, label, qualified_name.str());
345+
std::string signature;
346+
std::string return_type;
347+
auto* ccs =
348+
candidate.CreateCodeCompletionString(sema,
349+
context,
350+
getAllocator(),
351+
getCodeCompletionTUInfo(),
352+
/*IncludeBriefComments=*/false);
353+
if(ccs) {
354+
signature = extract_signature(*ccs);
355+
return_type = extract_return_type(*ccs);
356+
}
357+
358+
try_add(label, kind, label, qualified_name.str(), signature, return_type);
255359
break;
256360
}
257361
}
258362
}
259363

260364
for(auto& entry: overloads) {
261365
if(entry.count > 1) {
262-
entry.item.detail = "(...)";
366+
protocol::CompletionItemLabelDetails details;
367+
details.detail = std::format("(…) +{} overloads", entry.count);
368+
entry.item.label_details = std::move(details);
263369
}
264370
collected.push_back(std::move(entry.item));
265371
}
266372

373+
// In bundle mode, deduplicate by label: when the same name appears as
374+
// both a class and its constructors/deduction guides, keep only the
375+
// highest-priority kind (Class > Function/Method > others).
376+
if(options.bundle_overloads) {
377+
auto kind_priority = [](protocol::CompletionItemKind k) -> int {
378+
switch(k) {
379+
case protocol::CompletionItemKind::Class:
380+
case protocol::CompletionItemKind::Struct: return 3;
381+
case protocol::CompletionItemKind::Function:
382+
case protocol::CompletionItemKind::Method: return 2;
383+
case protocol::CompletionItemKind::Constructor: return 1;
384+
default: return 0;
385+
}
386+
};
387+
388+
std::unordered_map<std::string, std::size_t> label_index;
389+
std::vector<protocol::CompletionItem> deduped;
390+
deduped.reserve(collected.size());
391+
392+
for(auto& item: collected) {
393+
auto [it, inserted] = label_index.try_emplace(item.label, deduped.size());
394+
if(inserted) {
395+
deduped.push_back(std::move(item));
396+
} else {
397+
auto& existing = deduped[it->second];
398+
int old_prio = existing.kind.has_value() ? kind_priority(*existing.kind) : 0;
399+
int new_prio = item.kind.has_value() ? kind_priority(*item.kind) : 0;
400+
if(new_prio > old_prio) {
401+
existing = std::move(item);
402+
}
403+
}
404+
}
405+
collected.swap(deduped);
406+
}
407+
267408
output.clear();
268409
output.swap(collected);
269410
}

0 commit comments

Comments
 (0)