Fix additionalTextEdits -> textEdit hack for VS Code (#89)

* Fix additionalTextEdits -> textEdit hack for VS Code

Visual Studio Code filters the completion result according to
textEdit.range and filterText, if the textEdit.range overlaps with
existing text, we have to include it in filterText, otherwise it would
be filtered out.

* Fix has_open_paren in FilterCandidates
This commit is contained in:
Riatre Foo 2018-10-10 06:36:39 +08:00 committed by Fangrui Song
parent 49dd0ed558
commit 3de62168c8

View File

@ -143,9 +143,20 @@ template <typename T> char *tofixedbase64(T input, char *out) {
// when given 1000+ completion items. // when given 1000+ completion items.
void FilterCandidates(Out_TextDocumentComplete *complete_response, void FilterCandidates(Out_TextDocumentComplete *complete_response,
const std::string &complete_text, lsPosition begin_pos, const std::string &complete_text, lsPosition begin_pos,
lsPosition end_pos, bool has_open_paren) { lsPosition end_pos, const std::string &buffer_line) {
assert(begin_pos.line == end_pos.line);
auto &items = complete_response->result.items; auto &items = complete_response->result.items;
// People usually does not want to insert snippets or parenthesis when
// changing function or type names, e.g. "str.|()" or "std::|<int>".
bool has_open_paren = false;
for (int c = end_pos.character; c < buffer_line.size(); ++c) {
if (buffer_line[c] == '(' || buffer_line[c] == '<')
has_open_paren = true;
if (!isspace(buffer_line[c]))
break;
}
auto finalize = [&]() { auto finalize = [&]() {
int max_num = g_config->completion.maxNum; int max_num = g_config->completion.maxNum;
if (items.size() > max_num) { if (items.size() > max_num) {
@ -155,14 +166,21 @@ void FilterCandidates(Out_TextDocumentComplete *complete_response,
for (auto &item : items) { for (auto &item : items) {
item.textEdit.range = lsRange{begin_pos, end_pos}; item.textEdit.range = lsRange{begin_pos, end_pos};
if (has_open_paren) if (has_open_paren && item.filterText)
item.textEdit.newText = item.label; item.textEdit.newText = item.filterText.value();
// https://github.com/Microsoft/language-server-protocol/issues/543 // https://github.com/Microsoft/language-server-protocol/issues/543
// Order of textEdit and additionalTextEdits is unspecified. // Order of textEdit and additionalTextEdits is unspecified.
auto &edits = item.additionalTextEdits; auto &edits = item.additionalTextEdits;
if (edits.size() && edits[0].range.end == begin_pos) { if (edits.size() && edits[0].range.end == begin_pos) {
item.textEdit.range.start = edits[0].range.start; lsPosition start = edits[0].range.start, end = edits[0].range.end;
item.textEdit.range.start = start;
item.textEdit.newText = edits[0].newText + item.textEdit.newText; item.textEdit.newText = edits[0].newText + item.textEdit.newText;
if (start.line == begin_pos.line && item.filterText) {
item.filterText =
buffer_line.substr(start.character,
end.character - start.character) +
item.filterText.value();
}
edits.erase(edits.begin()); edits.erase(edits.begin());
} }
// Compatibility // Compatibility
@ -217,27 +235,6 @@ void FilterCandidates(Out_TextDocumentComplete *complete_response,
finalize(); finalize();
} }
// Returns true if position is an points to a '(' character in |lines|. Skips
// whitespace.
bool IsOpenParenOrAngle(const std::vector<std::string> &lines,
const lsPosition &position) {
auto [c, l] = position;
while (l < lines.size()) {
const auto &line = lines[l];
if (c >= line.size())
return false;
if (line[c] == '(' || line[c] == '<')
return true;
if (!isspace(line[c]))
break;
if (++c >= line.size()) {
c = 0;
l++;
}
}
return false;
}
lsCompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) { lsCompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) {
switch (cursor_kind) { switch (cursor_kind) {
case CXCursor_UnexposedDecl: case CXCursor_UnexposedDecl:
@ -550,7 +547,6 @@ struct Handler_TextDocumentCompletion
params.position, &completion_text, &end_pos); params.position, &completion_text, &end_pos);
ParseIncludeLineResult preprocess = ParseIncludeLine(buffer_line); ParseIncludeLineResult preprocess = ParseIncludeLine(buffer_line);
bool has_open_paren = IsOpenParenOrAngle(file->buffer_lines, end_pos);
if (preprocess.ok && preprocess.keyword.compare("include") == 0) { if (preprocess.ok && preprocess.keyword.compare("include") == 0) {
Out_TextDocumentComplete out; Out_TextDocumentComplete out;
@ -568,14 +564,14 @@ struct Handler_TextDocumentCompletion
begin_pos.character = 0; begin_pos.character = 0;
end_pos.character = (int)buffer_line.size(); end_pos.character = (int)buffer_line.size();
FilterCandidates(&out, preprocess.pattern, begin_pos, end_pos, FilterCandidates(&out, preprocess.pattern, begin_pos, end_pos,
has_open_paren); buffer_line);
DecorateIncludePaths(preprocess.match, &out.result.items); DecorateIncludePaths(preprocess.match, &out.result.items);
pipeline::WriteStdout(kMethodType, out); pipeline::WriteStdout(kMethodType, out);
} else { } else {
std::string path = params.textDocument.uri.GetPath(); std::string path = params.textDocument.uri.GetPath();
CompletionManager::OnComplete callback = CompletionManager::OnComplete callback =
[completion_text, path, begin_pos, end_pos, has_open_paren, [completion_text, path, begin_pos, end_pos,
id = request->id](CodeCompleteConsumer *OptConsumer) { id = request->id, buffer_line](CodeCompleteConsumer *OptConsumer) {
if (!OptConsumer) if (!OptConsumer)
return; return;
auto *Consumer = static_cast<CompletionConsumer *>(OptConsumer); auto *Consumer = static_cast<CompletionConsumer *>(OptConsumer);
@ -584,7 +580,7 @@ struct Handler_TextDocumentCompletion
out.result.items = Consumer->ls_items; out.result.items = Consumer->ls_items;
FilterCandidates(&out, completion_text, begin_pos, end_pos, FilterCandidates(&out, completion_text, begin_pos, end_pos,
has_open_paren); buffer_line);
pipeline::WriteStdout(kMethodType, out); pipeline::WriteStdout(kMethodType, out);
if (!Consumer->from_cache) { if (!Consumer->from_cache) {
cache.WithLock([&]() { cache.WithLock([&]() {