Skip to content
This repository was archived by the owner on Jul 30, 2020. It is now read-only.

Commit 16dd874

Browse files
SarcasmMaskRay
authored andcommitted
add detailedLabel completion style
Some completion UI, such as Emacs' completion-at-point and company-lsp, display completion item label and detail side by side. This does not look right, when you see things like: "foo" "int foo()" "bar" "void bar(int i = 0)" When this option is enabled, the completion item label is very detailed, it shows the full signature of the candidate. The detail just contains the completion item parent context. Also, in this mode, functions with default arguments, generates one more item per default argument so that the right function call can be selected. That is, you get something like: "int foo()" "Foo" "void bar()" "Foo" "void bar(int i = 0)" "Foo" Be wary, this is quickly quite verbose, items can end up truncated by the UIs.
1 parent 3b92bab commit 16dd874

File tree

4 files changed

+187
-36
lines changed

4 files changed

+187
-36
lines changed

src/clang_complete.cc

Lines changed: 144 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,16 @@ unsigned Flags() {
2929

3030
unsigned GetCompletionPriority(const CXCompletionString& str,
3131
CXCursorKind result_kind,
32-
const std::string& label) {
32+
const std::string& typedText) {
3333
unsigned priority = clang_getCompletionPriority(str);
3434

3535
// XXX: What happens if priority overflows?
3636
if (result_kind == CXCursor_Destructor) {
3737
priority *= 100;
3838
}
3939
if (result_kind == CXCursor_ConversionFunction ||
40-
(result_kind == CXCursor_CXXMethod && StartsWith(label, "operator"))) {
40+
(result_kind == CXCursor_CXXMethod &&
41+
StartsWith(typedText, "operator"))) {
4142
priority *= 100;
4243
}
4344
if (clang_getCompletionAvailability(str) != CXAvailability_Available) {
@@ -149,6 +150,103 @@ lsCompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) {
149150
}
150151
}
151152

153+
void BuildCompletionItemTexts(std::vector<lsCompletionItem>& out,
154+
CXCompletionString completion_string,
155+
bool include_snippets) {
156+
assert(!out.empty());
157+
auto out_first = out.size() - 1;
158+
159+
std::string result_type;
160+
161+
int num_chunks = clang_getNumCompletionChunks(completion_string);
162+
for (int i = 0; i < num_chunks; ++i) {
163+
CXCompletionChunkKind kind =
164+
clang_getCompletionChunkKind(completion_string, i);
165+
166+
std::string text;
167+
switch (kind) {
168+
case CXCompletionChunk_LeftParen: text = '('; break;
169+
case CXCompletionChunk_RightParen: text = ')'; break;
170+
case CXCompletionChunk_LeftBracket: text = '['; break;
171+
case CXCompletionChunk_RightBracket: text = ']'; break;
172+
case CXCompletionChunk_LeftBrace: text = '{'; break;
173+
case CXCompletionChunk_RightBrace: text = '}'; break;
174+
case CXCompletionChunk_LeftAngle: text = '<'; break;
175+
case CXCompletionChunk_RightAngle: text = '>'; break;
176+
case CXCompletionChunk_Comma: text = ", "; break;
177+
case CXCompletionChunk_Colon: text = ':'; break;
178+
case CXCompletionChunk_SemiColon: text = ';'; break;
179+
case CXCompletionChunk_Equal: text = '='; break;
180+
case CXCompletionChunk_HorizontalSpace: text = ' '; break;
181+
case CXCompletionChunk_VerticalSpace: text = ' '; break;
182+
183+
case CXCompletionChunk_ResultType:
184+
result_type =
185+
ToString(clang_getCompletionChunkText(completion_string, i));
186+
continue;
187+
188+
case CXCompletionChunk_TypedText:
189+
case CXCompletionChunk_Placeholder:
190+
case CXCompletionChunk_Text:
191+
case CXCompletionChunk_Informative:
192+
text = ToString(clang_getCompletionChunkText(completion_string, i));
193+
194+
for (auto i = out_first; i < out.size(); ++i) {
195+
// first typed text is used for filtering
196+
if (kind == CXCompletionChunk_TypedText && out[i].filterText.empty())
197+
out[i].filterText = text;
198+
199+
if (kind == CXCompletionChunk_Placeholder)
200+
out[i].parameters_.push_back(text);
201+
}
202+
break;
203+
204+
case CXCompletionChunk_CurrentParameter:
205+
// We have our own parsing logic for active parameter. This doesn't seem
206+
// to be very reliable.
207+
continue;
208+
209+
case CXCompletionChunk_Optional: {
210+
CXCompletionString nested =
211+
clang_getCompletionChunkCompletionString(completion_string, i);
212+
// duplicate last element, the recursive call will complete it
213+
out.push_back(out.back());
214+
BuildCompletionItemTexts(out, nested, include_snippets);
215+
continue;
216+
}
217+
}
218+
219+
for (auto i = out_first; i < out.size(); ++i)
220+
out[i].label += text;
221+
222+
if (kind == CXCompletionChunk_Informative)
223+
continue;
224+
225+
for (auto i = out_first; i < out.size(); ++i) {
226+
if (!include_snippets && !out[i].parameters_.empty())
227+
continue;
228+
229+
if (kind == CXCompletionChunk_Placeholder) {
230+
out[i].insertText +=
231+
"${" + std::to_string(out[i].parameters_.size()) + ":" + text + "}";
232+
out[i].insertTextFormat = lsInsertTextFormat::Snippet;
233+
} else {
234+
out[i].insertText += text;
235+
}
236+
}
237+
}
238+
239+
if (result_type.empty())
240+
return;
241+
242+
for (auto i = out_first; i < out.size(); ++i) {
243+
// ' : ' for variables,
244+
// ' -> ' (trailing return type-like) for functions
245+
out[i].label += (out[i].label == out[i].filterText ? " : " : " -> ");
246+
out[i].label += result_type;
247+
}
248+
}
249+
152250
// |do_insert|: if |!do_insert|, do not append strings to |insert| after
153251
// a placeholder.
154252
void BuildDetailString(CXCompletionString completion_string,
@@ -387,6 +485,8 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) {
387485
{
388486
if (request->on_complete) {
389487
std::vector<lsCompletionItem> ls_result;
488+
// this is a guess but can be larger in case of optional parameters,
489+
// as they may be expanded into multiple items
390490
ls_result.reserve(cx_results->NumResults);
391491

392492
timer.Reset();
@@ -407,29 +507,52 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) {
407507
// TODO: fill in more data
408508
lsCompletionItem ls_completion_item;
409509

410-
bool do_insert = true;
411-
// kind/label/detail/docs/sortText
412510
ls_completion_item.kind = GetCompletionKind(result.CursorKind);
413-
BuildDetailString(
414-
result.CompletionString, ls_completion_item.label,
415-
ls_completion_item.detail, ls_completion_item.insertText,
416-
do_insert, ls_completion_item.insertTextFormat,
417-
&ls_completion_item.parameters_,
418-
completion_manager->config_->client.snippetSupport);
419-
if (completion_manager->config_->client.snippetSupport &&
420-
ls_completion_item.insertTextFormat ==
421-
lsInsertTextFormat::Snippet) {
422-
ls_completion_item.insertText += "$0";
423-
}
424-
425511
ls_completion_item.documentation = ToString(
426512
clang_getCompletionBriefComment(result.CompletionString));
427513

428-
ls_completion_item.priority_ = GetCompletionPriority(
429-
result.CompletionString, result.CursorKind,
430-
ls_completion_item.label);
431-
432-
ls_result.push_back(ls_completion_item);
514+
// label/detail/filterText/insertText/priority
515+
if (completion_manager->config_->completion.detailedLabel) {
516+
ls_completion_item.detail = ToString(
517+
clang_getCompletionParent(result.CompletionString, nullptr));
518+
519+
auto first_idx = ls_result.size();
520+
ls_result.push_back(ls_completion_item);
521+
522+
// label/filterText/insertText
523+
BuildCompletionItemTexts(
524+
ls_result, result.CompletionString,
525+
completion_manager->config_->client.snippetSupport);
526+
527+
for (auto i = first_idx; i < ls_result.size(); ++i) {
528+
if (completion_manager->config_->client.snippetSupport &&
529+
ls_result[i].insertTextFormat ==
530+
lsInsertTextFormat::Snippet) {
531+
ls_result[i].insertText += "$0";
532+
}
533+
534+
ls_result[i].priority_ = GetCompletionPriority(
535+
result.CompletionString, result.CursorKind,
536+
ls_result[i].filterText);
537+
}
538+
} else {
539+
bool do_insert = true;
540+
BuildDetailString(
541+
result.CompletionString, ls_completion_item.label,
542+
ls_completion_item.detail, ls_completion_item.insertText,
543+
do_insert, ls_completion_item.insertTextFormat,
544+
&ls_completion_item.parameters_,
545+
completion_manager->config_->client.snippetSupport);
546+
if (completion_manager->config_->client.snippetSupport &&
547+
ls_completion_item.insertTextFormat ==
548+
lsInsertTextFormat::Snippet) {
549+
ls_completion_item.insertText += "$0";
550+
}
551+
ls_completion_item.priority_ = GetCompletionPriority(
552+
result.CompletionString, result.CursorKind,
553+
ls_completion_item.label);
554+
ls_result.push_back(ls_completion_item);
555+
}
433556
}
434557

435558
timer.ResetAndPrint("[complete] Building " +

src/config.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,25 @@ struct Config {
135135
// false for the option. This option is the most useful for LSP clients
136136
// that implement their own filtering and sorting logic.
137137
bool filterAndSort = true;
138+
139+
// Some completion UI, such as Emacs' completion-at-point and company-lsp,
140+
// display completion item label and detail side by side.
141+
// This does not look right, when you see things like:
142+
// "foo" "int foo()"
143+
// "bar" "void bar(int i = 0)"
144+
// When this option is enabled, the completion item label is very detailed,
145+
// it shows the full signature of the candidate.
146+
// The detail just contains the completion item parent context.
147+
// Also, in this mode, functions with default arguments,
148+
// generates one more item per default argument
149+
// so that the right function call can be selected.
150+
// That is, you get something like:
151+
// "int foo()" "Foo"
152+
// "void bar()" "Foo"
153+
// "void bar(int i = 0)" "Foo"
154+
// Be wary, this is quickly quite verbose,
155+
// items can end up truncated by the UIs.
156+
bool detailedLabel = false;
138157
};
139158
Completion completion;
140159

@@ -162,7 +181,7 @@ struct Config {
162181
std::vector<std::string> dumpAST;
163182
};
164183
MAKE_REFLECT_STRUCT(Config::ClientCapability, snippetSupport);
165-
MAKE_REFLECT_STRUCT(Config::Completion, filterAndSort);
184+
MAKE_REFLECT_STRUCT(Config::Completion, filterAndSort, detailedLabel);
166185
MAKE_REFLECT_STRUCT(Config::Index, comments, attributeMakeCallsToCtor);
167186
MAKE_REFLECT_STRUCT(Config,
168187
compilationDatabaseDirectory,

src/language_server_api.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ struct lsCompletionItem {
365365

366366
// A string that should be used when filtering a set of
367367
// completion items. When `falsy` the label is used.
368-
// std::string filterText;
368+
std::string filterText;
369369

370370
// A string that should be inserted a document when selecting
371371
// this completion. When `falsy` the label is used.
@@ -407,6 +407,7 @@ MAKE_REFLECT_STRUCT(lsCompletionItem,
407407
documentation,
408408
sortText,
409409
insertText,
410+
filterText,
410411
insertTextFormat,
411412
textEdit);
412413

src/messages/text_document_completion.cc

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,18 @@ struct Out_TextDocumentComplete
6868
};
6969
MAKE_REFLECT_STRUCT(Out_TextDocumentComplete, jsonrpc, id, result);
7070

71-
bool CompareLsCompletionItem(const lsCompletionItem& item1,
72-
const lsCompletionItem& item2) {
73-
if (item1.found_ != item2.found_)
74-
return item1.found_ > item2.found_;
75-
if (item1.skip_ != item2.skip_)
76-
return item1.skip_ < item2.skip_;
77-
if (item1.priority_ != item2.priority_)
78-
return item1.priority_ < item2.priority_;
79-
if (item1.label.length() != item2.label.length())
80-
return item1.label.length() < item2.label.length();
81-
return item1.label < item2.label;
71+
bool CompareLsCompletionItem(const lsCompletionItem& lhs,
72+
const lsCompletionItem& rhs) {
73+
bool lhsNotFound = !lhs.found_;
74+
bool rhsNotFound = !rhs.found_;
75+
const auto lhsFilterTextLength = lhs.filterText.length();
76+
const auto lhsLabelLength = lhs.label.length();
77+
const auto rhsFilterTextLength = rhs.filterText.length();
78+
const auto rhsLabelLength = rhs.label.length();
79+
return std::tie(lhsNotFound, lhs.skip_, lhs.priority_, lhsFilterTextLength,
80+
lhs.filterText, lhsLabelLength, lhs.label) <
81+
std::tie(rhsNotFound, rhs.skip_, rhs.priority_, rhsFilterTextLength,
82+
rhs.filterText, rhsLabelLength, lhs.label);
8283
}
8384

8485
template <typename T>
@@ -129,11 +130,18 @@ void FilterAndSortCompletionResponse(
129130
return;
130131
}
131132

133+
// Make sure all items have |filterText| set, code that follow needs it.
134+
for (auto& item : items) {
135+
if (item.filterText.empty()) {
136+
item.filterText = item.label;
137+
}
138+
}
139+
132140
// If the text doesn't start with underscore,
133141
// remove all candidates that start with underscore.
134142
if (!complete_text.empty() && complete_text[0] != '_') {
135143
auto filter = [](const lsCompletionItem& item) {
136-
return item.label[0] == '_';
144+
return item.filterText[0] == '_';
137145
};
138146
items.erase(std::remove_if(items.begin(), items.end(), filter),
139147
items.end());
@@ -142,7 +150,7 @@ void FilterAndSortCompletionResponse(
142150
// Fuzzy match.
143151
for (auto& item : items)
144152
std::tie(item.found_, item.skip_) =
145-
SubsequenceCountSkip(complete_text, item.label);
153+
SubsequenceCountSkip(complete_text, item.filterText);
146154

147155
// Order all items and set |sortText|.
148156
std::sort(items.begin(), items.end(), CompareLsCompletionItem);

0 commit comments

Comments
 (0)