Backport recent update of completion

This commit is contained in:
Fangrui Song 2018-04-14 09:52:17 -07:00
parent 43d1ec144c
commit e522ce8179
6 changed files with 267 additions and 224 deletions

View File

@ -270,7 +270,8 @@ void BuildDetailString(CXCompletionString completion_string,
bool& do_insert,
lsInsertTextFormat& format,
std::vector<std::string>* parameters,
bool include_snippets) {
bool include_snippets,
int& angle_stack) {
int num_chunks = clang_getNumCompletionChunks(completion_string);
auto append = [&](const char* text) {
detail += text;
@ -285,8 +286,11 @@ void BuildDetailString(CXCompletionString completion_string,
case CXCompletionChunk_Optional: {
CXCompletionString nested =
clang_getCompletionChunkCompletionString(completion_string, i);
BuildDetailString(nested, label, detail, insert, do_insert, format,
parameters, include_snippets);
// Do not add text to insert string if we're in angle brackets.
bool should_insert = do_insert && angle_stack == 0;
BuildDetailString(nested, label, detail, insert,
should_insert, format, parameters,
include_snippets, angle_stack);
break;
}
@ -348,8 +352,8 @@ void BuildDetailString(CXCompletionString completion_string,
case CXCompletionChunk_RightBracket: append("]"); break;
case CXCompletionChunk_LeftBrace: append("{"); break;
case CXCompletionChunk_RightBrace: append("}"); break;
case CXCompletionChunk_LeftAngle: append("<"); break;
case CXCompletionChunk_RightAngle: append(">"); break;
case CXCompletionChunk_LeftAngle: append("<"); angle_stack++; break;
case CXCompletionChunk_RightAngle: append(">"); angle_stack--; break;
case CXCompletionChunk_Comma: append(", "); break;
case CXCompletionChunk_Colon: append(":"); break;
case CXCompletionChunk_SemiColon: append(";"); break;
@ -366,7 +370,8 @@ void BuildDetailString(CXCompletionString completion_string,
void TryEnsureDocumentParsed(ClangCompleteManager* manager,
std::shared_ptr<CompletionSession> session,
std::unique_ptr<ClangTranslationUnit>* tu,
ClangIndex* index) {
ClangIndex* index,
bool emit_diag) {
// Nothing to do. We already have a translation unit.
if (*tu)
return;
@ -414,11 +419,10 @@ void TryEnsureDocumentParsed(ClangCompleteManager* manager,
}
}
void CompletionParseMain(ClangCompleteManager* completion_manager) {
void CompletionPreloadMain(ClangCompleteManager* completion_manager) {
while (true) {
// Fetching the completion request blocks until we have a request.
ClangCompleteManager::ParseRequest request =
completion_manager->parse_requests_.Dequeue();
auto request = completion_manager->preload_requests_.Dequeue();
// If we don't get a session then that means we don't care about the file
// anymore - abandon the request.
@ -429,23 +433,23 @@ void CompletionParseMain(ClangCompleteManager* completion_manager) {
if (!session)
continue;
// Note: we only preload completion. We emit diagnostics for the
// completion preload though.
CompletionSession::Tu* tu = &session->completion;
// If we've parsed it more recently than the request time, don't bother
// reparsing.
if (session->tu_last_parsed_at &&
*session->tu_last_parsed_at > request.request_time) {
if (tu->last_parsed_at && *tu->last_parsed_at > request.request_time)
continue;
}
std::unique_ptr<ClangTranslationUnit> parsing;
TryEnsureDocumentParsed(completion_manager, session, &parsing,
&session->index);
TryEnsureDocumentParsed(completion_manager, session, &parsing, &tu->index,
true);
// Activate new translation unit.
// tu_last_parsed_at is only read by this thread, so it doesn't need to be
// under the mutex.
session->tu_last_parsed_at = std::chrono::high_resolution_clock::now();
std::lock_guard<std::mutex> lock(session->tu_lock);
session->tu = std::move(parsing);
std::lock_guard<std::mutex> lock(tu->lock);
tu->last_parsed_at = std::chrono::high_resolution_clock::now();
tu->tu = std::move(parsing);
}
}
@ -468,15 +472,15 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) {
completion_manager->TryGetSession(path, true /*mark_as_completion*/,
true /*create_if_needed*/);
std::lock_guard<std::mutex> lock(session->tu_lock);
std::lock_guard<std::mutex> lock(session->completion.lock);
Timer timer;
TryEnsureDocumentParsed(completion_manager, session, &session->tu,
&session->index);
TryEnsureDocumentParsed(completion_manager, session, &session->completion.tu,
&session->completion.index, false);
timer.ResetAndPrint("[complete] TryEnsureDocumentParsed");
// It is possible we failed to create the document despite
// |TryEnsureDocumentParsed|.
if (!session->tu)
if (!session->completion.tu)
continue;
timer.Reset();
@ -485,30 +489,21 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) {
std::vector<CXUnsavedFile> unsaved = snapshot.AsUnsavedFiles();
timer.ResetAndPrint("[complete] Creating WorkingFile snapshot");
// Emit code completion data.
if (request->position) {
// Language server is 0-based, clang is 1-based.
unsigned line = request->position->line + 1;
unsigned column = request->position->character + 1;
timer.Reset();
unsigned const kCompleteOptions =
CXCodeComplete_IncludeMacros | CXCodeComplete_IncludeBriefComments;
CXCodeCompleteResults* cx_results = clang_codeCompleteAt(
session->tu->cx_tu, session->file.filename.c_str(), line, column,
session->completion.tu->cx_tu, session->file.filename.c_str(),
request->position.line + 1, request->position.character + 1,
unsaved.data(), (unsigned)unsaved.size(), kCompleteOptions);
timer.ResetAndPrint("[complete] clangCodeCompleteAt");
if (!cx_results) {
if (request->on_complete)
request->on_complete({}, false /*is_cached_result*/);
continue;
}
{
if (request->on_complete) {
std::vector<lsCompletionItem> ls_result;
// this is a guess but can be larger in case of std::optional parameters,
// as they may be expanded into multiple items
// this is a guess but can be larger in case of std::optional
// parameters, as they may be expanded into multiple items
ls_result.reserve(cx_results->NumResults);
timer.Reset();
@ -530,8 +525,8 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) {
lsCompletionItem ls_completion_item;
ls_completion_item.kind = GetCompletionKind(result.CursorKind);
ls_completion_item.documentation = ToString(
clang_getCompletionBriefComment(result.CompletionString));
ls_completion_item.documentation =
ToString(clang_getCompletionBriefComment(result.CompletionString));
// label/detail/filterText/insertText/priority
if (g_config->completion.detailedLabel) {
@ -542,36 +537,35 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) {
ls_result.push_back(ls_completion_item);
// label/filterText/insertText
BuildCompletionItemTexts(
ls_result, result.CompletionString,
BuildCompletionItemTexts(ls_result, result.CompletionString,
g_config->client.snippetSupport);
for (auto i = first_idx; i < ls_result.size(); ++i) {
if (g_config->client.snippetSupport &&
ls_result[i].insertTextFormat ==
lsInsertTextFormat::Snippet) {
ls_result[i].insertTextFormat == lsInsertTextFormat::Snippet) {
ls_result[i].insertText += "$0";
}
ls_result[i].priority_ = GetCompletionPriority(
result.CompletionString, result.CursorKind,
ls_result[i].priority_ =
GetCompletionPriority(result.CompletionString, result.CursorKind,
ls_result[i].filterText);
}
} else {
bool do_insert = true;
BuildDetailString(
result.CompletionString, ls_completion_item.label,
ls_completion_item.detail, ls_completion_item.insertText,
do_insert, ls_completion_item.insertTextFormat,
int angle_stack = 0;
BuildDetailString(result.CompletionString, ls_completion_item.label,
ls_completion_item.detail,
ls_completion_item.insertText, do_insert,
ls_completion_item.insertTextFormat,
&ls_completion_item.parameters_,
g_config->client.snippetSupport);
g_config->client.snippetSupport, angle_stack);
if (g_config->client.snippetSupport &&
ls_completion_item.insertTextFormat ==
lsInsertTextFormat::Snippet) {
ls_completion_item.insertText += "$0";
}
ls_completion_item.priority_ = GetCompletionPriority(
result.CompletionString, result.CursorKind,
ls_completion_item.priority_ =
GetCompletionPriority(result.CompletionString, result.CursorKind,
ls_completion_item.label);
ls_result.push_back(ls_completion_item);
}
@ -582,35 +576,60 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) {
" completion results");
request->on_complete(ls_result, false /*is_cached_result*/);
}
}
// Make sure |ls_results| is destroyed before clearing |cx_results|.
clang_disposeCodeCompleteResults(cx_results);
}
}
// Emit diagnostics.
if (request->emit_diagnostics) {
// TODO: before emitting diagnostics check if we have another completion
// request and think about servicing that first, because it may be much
// faster than reparsing the document.
// TODO: have a separate thread for diagnostics?
void DiagnosticQueryMain(ClangCompleteManager* completion_manager) {
while (true) {
// Fetching the completion request blocks until we have a request.
ClangCompleteManager::DiagnosticRequest request =
completion_manager->diagnostic_request_.Dequeue();
std::string path = request.document.uri.GetPath();
std::shared_ptr<CompletionSession> session =
completion_manager->TryGetSession(path, true /*mark_as_completion*/,
true /*create_if_needed*/);
// At this point, we must have a translation unit. Block until we have one.
std::lock_guard<std::mutex> lock(session->diagnostics.lock);
Timer timer;
TryEnsureDocumentParsed(
completion_manager, session, &session->diagnostics.tu,
&session->diagnostics.index, false /*emit_diagnostics*/);
timer.ResetAndPrint("[diagnostics] TryEnsureDocumentParsed");
// It is possible we failed to create the document despite
// |TryEnsureDocumentParsed|.
if (!session->diagnostics.tu)
continue;
timer.Reset();
session->tu =
ClangTranslationUnit::Reparse(std::move(session->tu), unsaved);
timer.ResetAndPrint("[complete] clang_reparseTranslationUnit");
if (!session->tu) {
WorkingFiles::Snapshot snapshot =
completion_manager->working_files_->AsSnapshot({StripFileType(path)});
std::vector<CXUnsavedFile> unsaved = snapshot.AsUnsavedFiles();
timer.ResetAndPrint("[diagnostics] Creating WorkingFile snapshot");
// Emit diagnostics.
timer.Reset();
session->diagnostics.tu = ClangTranslationUnit::Reparse(
std::move(session->diagnostics.tu), unsaved);
timer.ResetAndPrint("[diagnostics] clang_reparseTranslationUnit");
if (!session->diagnostics.tu) {
LOG_S(ERROR) << "Reparsing translation unit for diagnostics failed for "
<< path;
continue;
}
size_t num_diagnostics = clang_getNumDiagnostics(session->tu->cx_tu);
size_t num_diagnostics =
clang_getNumDiagnostics(session->diagnostics.tu->cx_tu);
std::vector<lsDiagnostic> ls_diagnostics;
ls_diagnostics.reserve(num_diagnostics);
for (unsigned i = 0; i < num_diagnostics; ++i) {
CXDiagnostic cx_diag = clang_getDiagnostic(session->tu->cx_tu, i);
CXDiagnostic cx_diag =
clang_getDiagnostic(session->diagnostics.tu->cx_tu, i);
std::optional<lsDiagnostic> diagnostic =
BuildAndDisposeDiagnostic(cx_diag, path);
// Filter messages like "too many errors emitted, stopping now
@ -619,51 +638,12 @@ void CompletionQueryMain(ClangCompleteManager* completion_manager) {
if (diagnostic && diagnostic->range.start.line >= 0)
ls_diagnostics.push_back(*diagnostic);
}
completion_manager->on_diagnostic_(session->file.filename,
ls_diagnostics);
/*
timer.Reset();
completion_manager->on_index_(session->tu.get(), unsaved,
session->file.filename, session->file.args);
timer.ResetAndPrint("[complete] Reindex file");
*/
}
continue;
completion_manager->on_diagnostic_(session->file.filename, ls_diagnostics);
}
}
} // namespace
CompletionSession::CompletionSession(const Project::Entry& file,
WorkingFiles* working_files)
: file(file),
working_files(working_files),
index(0 /*excludeDeclarationsFromPCH*/, 0 /*displayDiagnostics*/) {}
CompletionSession::~CompletionSession() {}
ClangCompleteManager::ParseRequest::ParseRequest(const std::string& path)
: request_time(std::chrono::high_resolution_clock::now()), path(path) {}
ClangCompleteManager::CompletionRequest::CompletionRequest(
const lsRequestId& id,
const lsTextDocumentIdentifier& document,
bool emit_diagnostics)
: id(id), document(document), emit_diagnostics(emit_diagnostics) {}
ClangCompleteManager::CompletionRequest::CompletionRequest(
const lsRequestId& id,
const lsTextDocumentIdentifier& document,
const lsPosition& position,
const OnComplete& on_complete,
bool emit_diagnostics)
: id(id),
document(document),
position(position),
on_complete(on_complete),
emit_diagnostics(emit_diagnostics) {}
ClangCompleteManager::ClangCompleteManager(Project* project,
WorkingFiles* working_files,
OnDiagnostic on_diagnostic,
@ -677,13 +657,16 @@ ClangCompleteManager::ClangCompleteManager(Project* project,
preloaded_sessions_(kMaxPreloadedSessions),
completion_sessions_(kMaxCompletionSessions) {
new std::thread([&]() {
SetThreadName("completequery");
SetThreadName("comp-query");
CompletionQueryMain(this);
});
new std::thread([&]() {
SetThreadName("completeparse");
CompletionParseMain(this);
SetThreadName("comp-preload");
CompletionPreloadMain(this);
});
new std::thread([&]() {
SetThreadName("diag-query");
DiagnosticQueryMain(this);
});
}
@ -693,14 +676,20 @@ void ClangCompleteManager::CodeComplete(
const OnComplete& on_complete) {
completion_request_.PushBack(std::make_unique<CompletionRequest>(
id, completion_location.textDocument, completion_location.position,
on_complete, false));
on_complete));
}
void ClangCompleteManager::DiagnosticsUpdate(
const lsRequestId& id,
const lsTextDocumentIdentifier& document) {
completion_request_.PushBack(
std::make_unique<CompletionRequest>(id, document, true));
bool has = false;
diagnostic_request_.Iterate([&](const DiagnosticRequest& request) {
if (request.document.uri == document.uri)
has = true;
});
if (!has)
diagnostic_request_.PushBack(DiagnosticRequest{document},
true /*priority*/);
}
void ClangCompleteManager::NotifyView(const std::string& filename) {
@ -712,7 +701,7 @@ void ClangCompleteManager::NotifyView(const std::string& filename) {
// Only reparse the file if we create a new CompletionSession.
if (EnsureCompletionOrCreatePreloadSession(filename))
parse_requests_.PushBack(ParseRequest(filename), true);
preload_requests_.PushBack(PreloadRequest(filename), true);
}
void ClangCompleteManager::NotifyEdit(const std::string& filename) {
@ -731,7 +720,7 @@ void ClangCompleteManager::NotifySave(const std::string& filename) {
//
EnsureCompletionOrCreatePreloadSession(filename);
parse_requests_.PushBack(ParseRequest(filename), true);
preload_requests_.PushBack(PreloadRequest(filename), true);
}
void ClangCompleteManager::NotifyClose(const std::string& filename) {

View File

@ -18,22 +18,26 @@
struct CompletionSession
: public std::enable_shared_from_this<CompletionSession> {
Project::Entry file;
WorkingFiles* working_files;
ClangIndex index;
// Translation unit for clang.
struct Tu {
ClangIndex index{0, 0};
// When |tu| was last parsed.
std::optional<std::chrono::time_point<std::chrono::high_resolution_clock>>
tu_last_parsed_at;
last_parsed_at;
// Acquired when |tu| is being used.
std::mutex tu_lock;
// The active translation unit.
std::mutex lock;
std::unique_ptr<ClangTranslationUnit> tu;
};
CompletionSession(const Project::Entry& file, WorkingFiles* working_files);
~CompletionSession();
Project::Entry file;
WorkingFiles* working_files;
Tu completion;
Tu diagnostics;
CompletionSession(const Project::Entry& file, WorkingFiles* wfiles)
: file(file), working_files(wfiles) {}
};
struct ClangCompleteManager {
@ -49,27 +53,30 @@ struct ClangCompleteManager {
bool is_cached_result)>;
using OnDropped = std::function<void(lsRequestId request_id)>;
struct ParseRequest {
ParseRequest(const std::string& path);
struct PreloadRequest {
PreloadRequest(const std::string& path)
: request_time(std::chrono::high_resolution_clock::now()), path(path) {}
std::chrono::time_point<std::chrono::high_resolution_clock> request_time;
std::string path;
};
struct CompletionRequest {
CompletionRequest(const lsRequestId& id,
const lsTextDocumentIdentifier& document,
bool emit_diagnostics);
CompletionRequest(const lsRequestId& id,
const lsTextDocumentIdentifier& document,
const lsPosition& position,
const OnComplete& on_complete,
bool emit_diagnostics);
const OnComplete& on_complete)
: id(id),
document(document),
position(position),
on_complete(on_complete) {}
lsRequestId id;
lsTextDocumentIdentifier document;
std::optional<lsPosition> position;
OnComplete on_complete; // May be null/empty.
bool emit_diagnostics = false;
lsPosition position;
OnComplete on_complete;
};
struct DiagnosticRequest {
lsTextDocumentIdentifier document;
};
ClangCompleteManager(Project* project,
@ -138,9 +145,10 @@ struct ClangCompleteManager {
// Request a code completion at the given location.
ThreadedQueue<std::unique_ptr<CompletionRequest>> completion_request_;
ThreadedQueue<DiagnosticRequest> diagnostic_request_;
// Parse requests. The path may already be parsed, in which case it should be
// reparsed.
ThreadedQueue<ParseRequest> parse_requests_;
ThreadedQueue<PreloadRequest> preload_requests_;
};
// Cached completion information, so we can give fast completion results when

View File

@ -123,7 +123,7 @@ static const std::vector<std::string> preprocessorKeywords = {
"define", "undef", "include", "if", "ifdef", "ifndef",
"else", "elif", "endif", "line", "error", "pragma"};
std::vector<lsCompletionItem> preprocessorKeywordCompletionItems(
std::vector<lsCompletionItem> PreprocessorKeywordCompletionItems(
const std::smatch& match) {
std::vector<lsCompletionItem> items;
for (auto& keyword : preprocessorKeywords) {
@ -160,7 +160,8 @@ char* tofixedbase64(T input, char* out) {
// when given 1000+ completion items.
void FilterAndSortCompletionResponse(
Out_TextDocumentComplete* complete_response,
const std::string& complete_text) {
const std::string& complete_text,
bool has_open_paren) {
if (!g_config->completion.filterAndSort)
return;
@ -189,6 +190,10 @@ void FilterAndSortCompletionResponse(
complete_response->result.isIncomplete = true;
}
if (has_open_paren)
for (auto& item: items)
item.insertText = item.label;
// Set sortText. Note that this happens after resizing - we could do it
// before, but then we should also sort by priority.
char buf[16];
@ -236,6 +241,26 @@ void FilterAndSortCompletionResponse(
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;
}
struct Handler_TextDocumentCompletion : MessageHandler {
MethodType GetMethodType() const override { return kMethodType; }
@ -306,13 +331,15 @@ struct Handler_TextDocumentCompletion : MessageHandler {
bool is_global_completion = false;
std::string existing_completion;
lsPosition end_pos = request->params.position;
if (file) {
request->params.position = file->FindStableCompletionSource(
request->params.position, &is_global_completion,
&existing_completion);
&existing_completion, &end_pos);
}
ParseIncludeLineResult result = ParseIncludeLine(buffer_line);
bool has_open_paren = IsOpenParenOrAngle(file->buffer_lines, end_pos);
if (result.ok) {
Out_TextDocumentComplete out;
@ -325,8 +352,8 @@ struct Handler_TextDocumentCompletion : MessageHandler {
[&result](std::string_view k) {
return k == result.keyword;
})) {
out.result.items = preprocessorKeywordCompletionItems(result.match);
FilterAndSortCompletionResponse(&out, result.keyword);
out.result.items = PreprocessorKeywordCompletionItems(result.match);
FilterAndSortCompletionResponse(&out, result.keyword, has_open_paren);
}
} else if (result.keyword.compare("include") == 0) {
{
@ -340,7 +367,7 @@ struct Handler_TextDocumentCompletion : MessageHandler {
if (quote.empty() || quote == (item.use_angle_brackets_ ? "<" : "\""))
out.result.items.push_back(item);
}
FilterAndSortCompletionResponse(&out, result.pattern);
FilterAndSortCompletionResponse(&out, result.pattern, has_open_paren);
DecorateIncludePaths(result.match, &out.result.items);
}
@ -354,15 +381,15 @@ struct Handler_TextDocumentCompletion : MessageHandler {
QueueManager::WriteStdout(kMethodType, out);
} else {
ClangCompleteManager::OnComplete callback = std::bind(
[this, is_global_completion, existing_completion, request](
const std::vector<lsCompletionItem>& results,
[this, request, is_global_completion, existing_completion,
has_open_paren](const std::vector<lsCompletionItem>& results,
bool is_cached_result) {
Out_TextDocumentComplete out;
out.id = request->id;
out.result.items = results;
// Emit completion results.
FilterAndSortCompletionResponse(&out, existing_completion);
FilterAndSortCompletionResponse(&out, existing_completion, has_open_paren);
QueueManager::WriteStdout(kMethodType, out);
// Cache completion results.

View File

@ -205,6 +205,15 @@ struct ThreadedQueue : public BaseThreadQueue {
std::optional<T> TryPopFrontHigh() { return TryPopFrontHelper(2); }
template <typename Fn>
void Iterate(Fn fn) {
std::lock_guard<std::mutex> lock(mutex_);
for (auto& entry : priority_)
fn(entry);
for (auto& entry : queue_)
fn(entry);
}
mutable std::mutex mutex_;
private:

View File

@ -415,7 +415,8 @@ std::string WorkingFile::FindClosestCallNameInBuffer(
lsPosition WorkingFile::FindStableCompletionSource(
lsPosition position,
bool* is_global_completion,
std::string* existing_completion) const {
std::string* existing_completion,
lsPosition* replace_end_pos) const {
*is_global_completion = true;
int start_offset = GetOffsetForPosition(position, buffer_content);
@ -441,6 +442,14 @@ lsPosition WorkingFile::FindStableCompletionSource(
--offset;
}
*replace_end_pos = position;
for (int i = start_offset; i < buffer_content.size(); i++) {
char c = buffer_content[i];
if (!isalnum(c) && c != '_') break;
// We know that replace_end_pos and position are on the same line.
replace_end_pos->character++;
}
*existing_completion = buffer_content.substr(offset, start_offset - offset);
return GetPositionForOffset(buffer_content, offset);
}

View File

@ -69,7 +69,8 @@ struct WorkingFile {
// content the user has entered.
lsPosition FindStableCompletionSource(lsPosition position,
bool* is_global_completion,
std::string* existing_completion) const;
std::string* existing_completion,
lsPosition* replace_end_pos) const;
private:
// Compute index_to_buffer and buffer_to_index.