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

View File

@ -18,22 +18,26 @@
struct CompletionSession struct CompletionSession
: public std::enable_shared_from_this<CompletionSession> { : public std::enable_shared_from_this<CompletionSession> {
// 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>>
last_parsed_at;
// Acquired when |tu| is being used.
std::mutex lock;
std::unique_ptr<ClangTranslationUnit> tu;
};
Project::Entry file; Project::Entry file;
WorkingFiles* working_files; WorkingFiles* working_files;
ClangIndex index;
// When |tu| was last parsed. Tu completion;
std::optional<std::chrono::time_point<std::chrono::high_resolution_clock>> Tu diagnostics;
tu_last_parsed_at;
// Acquired when |tu| is being used. CompletionSession(const Project::Entry& file, WorkingFiles* wfiles)
std::mutex tu_lock; : file(file), working_files(wfiles) {}
// The active translation unit.
std::unique_ptr<ClangTranslationUnit> tu;
CompletionSession(const Project::Entry& file, WorkingFiles* working_files);
~CompletionSession();
}; };
struct ClangCompleteManager { struct ClangCompleteManager {
@ -49,27 +53,30 @@ struct ClangCompleteManager {
bool is_cached_result)>; bool is_cached_result)>;
using OnDropped = std::function<void(lsRequestId request_id)>; using OnDropped = std::function<void(lsRequestId request_id)>;
struct ParseRequest { struct PreloadRequest {
ParseRequest(const std::string& path); 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::chrono::time_point<std::chrono::high_resolution_clock> request_time;
std::string path; std::string path;
}; };
struct CompletionRequest { struct CompletionRequest {
CompletionRequest(const lsRequestId& id,
const lsTextDocumentIdentifier& document,
bool emit_diagnostics);
CompletionRequest(const lsRequestId& id, CompletionRequest(const lsRequestId& id,
const lsTextDocumentIdentifier& document, const lsTextDocumentIdentifier& document,
const lsPosition& position, const lsPosition& position,
const OnComplete& on_complete, const OnComplete& on_complete)
bool emit_diagnostics); : id(id),
document(document),
position(position),
on_complete(on_complete) {}
lsRequestId id; lsRequestId id;
lsTextDocumentIdentifier document; lsTextDocumentIdentifier document;
std::optional<lsPosition> position; lsPosition position;
OnComplete on_complete; // May be null/empty. OnComplete on_complete;
bool emit_diagnostics = false; };
struct DiagnosticRequest {
lsTextDocumentIdentifier document;
}; };
ClangCompleteManager(Project* project, ClangCompleteManager(Project* project,
@ -138,9 +145,10 @@ struct ClangCompleteManager {
// Request a code completion at the given location. // Request a code completion at the given location.
ThreadedQueue<std::unique_ptr<CompletionRequest>> completion_request_; 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 // Parse requests. The path may already be parsed, in which case it should be
// reparsed. // reparsed.
ThreadedQueue<ParseRequest> parse_requests_; ThreadedQueue<PreloadRequest> preload_requests_;
}; };
// Cached completion information, so we can give fast completion results when // 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", "define", "undef", "include", "if", "ifdef", "ifndef",
"else", "elif", "endif", "line", "error", "pragma"}; "else", "elif", "endif", "line", "error", "pragma"};
std::vector<lsCompletionItem> preprocessorKeywordCompletionItems( std::vector<lsCompletionItem> PreprocessorKeywordCompletionItems(
const std::smatch& match) { const std::smatch& match) {
std::vector<lsCompletionItem> items; std::vector<lsCompletionItem> items;
for (auto& keyword : preprocessorKeywords) { for (auto& keyword : preprocessorKeywords) {
@ -160,7 +160,8 @@ char* tofixedbase64(T input, char* out) {
// when given 1000+ completion items. // when given 1000+ completion items.
void FilterAndSortCompletionResponse( void FilterAndSortCompletionResponse(
Out_TextDocumentComplete* complete_response, Out_TextDocumentComplete* complete_response,
const std::string& complete_text) { const std::string& complete_text,
bool has_open_paren) {
if (!g_config->completion.filterAndSort) if (!g_config->completion.filterAndSort)
return; return;
@ -189,6 +190,10 @@ void FilterAndSortCompletionResponse(
complete_response->result.isIncomplete = true; 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 // Set sortText. Note that this happens after resizing - we could do it
// before, but then we should also sort by priority. // before, but then we should also sort by priority.
char buf[16]; char buf[16];
@ -236,6 +241,26 @@ void FilterAndSortCompletionResponse(
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;
}
struct Handler_TextDocumentCompletion : MessageHandler { struct Handler_TextDocumentCompletion : MessageHandler {
MethodType GetMethodType() const override { return kMethodType; } MethodType GetMethodType() const override { return kMethodType; }
@ -306,13 +331,15 @@ struct Handler_TextDocumentCompletion : MessageHandler {
bool is_global_completion = false; bool is_global_completion = false;
std::string existing_completion; std::string existing_completion;
lsPosition end_pos = request->params.position;
if (file) { if (file) {
request->params.position = file->FindStableCompletionSource( request->params.position = file->FindStableCompletionSource(
request->params.position, &is_global_completion, request->params.position, &is_global_completion,
&existing_completion); &existing_completion, &end_pos);
} }
ParseIncludeLineResult result = ParseIncludeLine(buffer_line); ParseIncludeLineResult result = ParseIncludeLine(buffer_line);
bool has_open_paren = IsOpenParenOrAngle(file->buffer_lines, end_pos);
if (result.ok) { if (result.ok) {
Out_TextDocumentComplete out; Out_TextDocumentComplete out;
@ -325,8 +352,8 @@ struct Handler_TextDocumentCompletion : MessageHandler {
[&result](std::string_view k) { [&result](std::string_view k) {
return k == result.keyword; return k == result.keyword;
})) { })) {
out.result.items = preprocessorKeywordCompletionItems(result.match); out.result.items = PreprocessorKeywordCompletionItems(result.match);
FilterAndSortCompletionResponse(&out, result.keyword); FilterAndSortCompletionResponse(&out, result.keyword, has_open_paren);
} }
} else if (result.keyword.compare("include") == 0) { } else if (result.keyword.compare("include") == 0) {
{ {
@ -340,7 +367,7 @@ struct Handler_TextDocumentCompletion : MessageHandler {
if (quote.empty() || quote == (item.use_angle_brackets_ ? "<" : "\"")) if (quote.empty() || quote == (item.use_angle_brackets_ ? "<" : "\""))
out.result.items.push_back(item); out.result.items.push_back(item);
} }
FilterAndSortCompletionResponse(&out, result.pattern); FilterAndSortCompletionResponse(&out, result.pattern, has_open_paren);
DecorateIncludePaths(result.match, &out.result.items); DecorateIncludePaths(result.match, &out.result.items);
} }
@ -354,15 +381,15 @@ struct Handler_TextDocumentCompletion : MessageHandler {
QueueManager::WriteStdout(kMethodType, out); QueueManager::WriteStdout(kMethodType, out);
} else { } else {
ClangCompleteManager::OnComplete callback = std::bind( ClangCompleteManager::OnComplete callback = std::bind(
[this, is_global_completion, existing_completion, request]( [this, request, is_global_completion, existing_completion,
const std::vector<lsCompletionItem>& results, has_open_paren](const std::vector<lsCompletionItem>& results,
bool is_cached_result) { bool is_cached_result) {
Out_TextDocumentComplete out; Out_TextDocumentComplete out;
out.id = request->id; out.id = request->id;
out.result.items = results; out.result.items = results;
// Emit completion results. // Emit completion results.
FilterAndSortCompletionResponse(&out, existing_completion); FilterAndSortCompletionResponse(&out, existing_completion, has_open_paren);
QueueManager::WriteStdout(kMethodType, out); QueueManager::WriteStdout(kMethodType, out);
// Cache completion results. // Cache completion results.

View File

@ -205,6 +205,15 @@ struct ThreadedQueue : public BaseThreadQueue {
std::optional<T> TryPopFrontHigh() { return TryPopFrontHelper(2); } 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_; mutable std::mutex mutex_;
private: private:

View File

@ -415,7 +415,8 @@ std::string WorkingFile::FindClosestCallNameInBuffer(
lsPosition WorkingFile::FindStableCompletionSource( lsPosition WorkingFile::FindStableCompletionSource(
lsPosition position, lsPosition position,
bool* is_global_completion, bool* is_global_completion,
std::string* existing_completion) const { std::string* existing_completion,
lsPosition* replace_end_pos) const {
*is_global_completion = true; *is_global_completion = true;
int start_offset = GetOffsetForPosition(position, buffer_content); int start_offset = GetOffsetForPosition(position, buffer_content);
@ -441,6 +442,14 @@ lsPosition WorkingFile::FindStableCompletionSource(
--offset; --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); *existing_completion = buffer_content.substr(offset, start_offset - offset);
return GetPositionForOffset(buffer_content, offset); return GetPositionForOffset(buffer_content, offset);
} }

View File

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