Publish diagnostics of inferred files and change diagnostics.{onChange,onOpen,onSave} from bool to debounce time

This commit is contained in:
Fangrui Song 2018-09-22 01:37:00 -07:00
parent a5b8def411
commit bf698b85d4
15 changed files with 185 additions and 228 deletions

View File

@ -6,6 +6,7 @@
#include "clang_utils.h" #include "clang_utils.h"
#include "filesystem.hh" #include "filesystem.hh"
#include "log.hh" #include "log.hh"
#include "match.h"
#include "platform.h" #include "platform.h"
#include <clang/Frontend/CompilerInstance.h> #include <clang/Frontend/CompilerInstance.h>
@ -20,7 +21,10 @@ using namespace clang;
using namespace llvm; using namespace llvm;
#include <algorithm> #include <algorithm>
#include <chrono>
#include <ratio>
#include <thread> #include <thread>
namespace chrono = std::chrono;
namespace ccls { namespace ccls {
namespace { namespace {
@ -68,22 +72,34 @@ class StoreDiags : public DiagnosticConsumer {
const LangOptions *LangOpts; const LangOptions *LangOpts;
std::optional<Diag> last; std::optional<Diag> last;
std::vector<Diag> output; std::vector<Diag> output;
std::string path;
std::unordered_map<unsigned, bool> FID2concerned;
void Flush() { void Flush() {
if (!last) if (!last)
return; return;
bool mentions = last->inside_main || last->edits.size(); bool mentions = last->concerned || last->edits.size();
if (!mentions) if (!mentions)
for (auto &N : last->notes) for (auto &N : last->notes)
if (N.inside_main) if (N.concerned)
mentions = true; mentions = true;
if (mentions) if (mentions)
output.push_back(std::move(*last)); output.push_back(std::move(*last));
last.reset(); last.reset();
} }
public: public:
StoreDiags(std::string path) : path(std::move(path)) {}
std::vector<Diag> Take() { std::vector<Diag> Take() {
return std::move(output); return std::move(output);
} }
bool IsConcerned(const SourceManager &SM, SourceLocation L) {
FileID FID = SM.getFileID(L);
auto it = FID2concerned.try_emplace(FID.getHashValue());
if (it.second) {
const FileEntry *FE = SM.getFileEntryForID(FID);
it.first->second = FE && FileName(*FE) == path;
}
return it.first->second;
}
void BeginSourceFile(const LangOptions &Opts, const Preprocessor *) override { void BeginSourceFile(const LangOptions &Opts, const Preprocessor *) override {
LangOpts = &Opts; LangOpts = &Opts;
} }
@ -96,24 +112,25 @@ public:
SourceLocation L = Info.getLocation(); SourceLocation L = Info.getLocation();
if (!L.isValid()) return; if (!L.isValid()) return;
const SourceManager &SM = Info.getSourceManager(); const SourceManager &SM = Info.getSourceManager();
bool inside_main = SM.isInMainFile(L); StringRef Filename = SM.getFilename(Info.getLocation());
bool concerned = IsConcerned(SM, Info.getLocation());
auto fillDiagBase = [&](DiagBase &d) { auto fillDiagBase = [&](DiagBase &d) {
llvm::SmallString<64> Message; llvm::SmallString<64> Message;
Info.FormatDiagnostic(Message); Info.FormatDiagnostic(Message);
d.range = d.range =
FromCharSourceRange(SM, *LangOpts, DiagnosticRange(Info, *LangOpts)); FromCharSourceRange(SM, *LangOpts, DiagnosticRange(Info, *LangOpts));
d.message = Message.str(); d.message = Message.str();
d.inside_main = inside_main; d.concerned = concerned;
d.file = SM.getFilename(Info.getLocation()); d.file = Filename;
d.level = Level; d.level = Level;
d.category = DiagnosticIDs::getCategoryNumberForDiag(Info.getID()); d.category = DiagnosticIDs::getCategoryNumberForDiag(Info.getID());
}; };
auto addFix = [&](bool SyntheticMessage) -> bool { auto addFix = [&](bool SyntheticMessage) -> bool {
if (!inside_main) if (!concerned)
return false; return false;
for (const FixItHint &FixIt : Info.getFixItHints()) { for (const FixItHint &FixIt : Info.getFixItHints()) {
if (!SM.isInMainFile(FixIt.RemoveRange.getBegin())) if (!IsConcerned(SM, FixIt.RemoveRange.getBegin()))
return false; return false;
lsTextEdit edit; lsTextEdit edit;
edit.newText = FixIt.CodeToInsert; edit.newText = FixIt.CodeToInsert;
@ -146,9 +163,10 @@ std::unique_ptr<CompilerInstance> BuildCompilerInstance(
CompletionSession &session, std::unique_ptr<CompilerInvocation> CI, CompletionSession &session, std::unique_ptr<CompilerInvocation> CI,
DiagnosticConsumer &DC, const WorkingFiles::Snapshot &snapshot, DiagnosticConsumer &DC, const WorkingFiles::Snapshot &snapshot,
std::vector<std::unique_ptr<llvm::MemoryBuffer>> &Bufs) { std::vector<std::unique_ptr<llvm::MemoryBuffer>> &Bufs) {
std::string main = ResolveIfRelative(session.file.directory, CI->getFrontendOpts().Inputs[0].getFile());
for (auto &file : snapshot.files) { for (auto &file : snapshot.files) {
Bufs.push_back(llvm::MemoryBuffer::getMemBuffer(file.content)); Bufs.push_back(llvm::MemoryBuffer::getMemBuffer(file.content));
if (file.filename == session.file.filename) { if (file.filename == main)
if (auto Preamble = session.GetPreamble()) { if (auto Preamble = session.GetPreamble()) {
#if LLVM_VERSION_MAJOR >= 7 #if LLVM_VERSION_MAJOR >= 7
Preamble->Preamble.OverridePreamble(*CI, session.FS, Preamble->Preamble.OverridePreamble(*CI, session.FS,
@ -157,14 +175,10 @@ std::unique_ptr<CompilerInstance> BuildCompilerInstance(
Preamble->Preamble.AddImplicitPreamble(*CI, session.FS, Preamble->Preamble.AddImplicitPreamble(*CI, session.FS,
Bufs.back().get()); Bufs.back().get());
#endif #endif
} else { continue;
CI->getPreprocessorOpts().addRemappedFile(
CI->getFrontendOpts().Inputs[0].getFile(), Bufs.back().get());
} }
} else { CI->getPreprocessorOpts().addRemappedFile(file.filename,
CI->getPreprocessorOpts().addRemappedFile(file.filename, Bufs.back().get());
Bufs.back().get());
}
} }
auto Clang = std::make_unique<CompilerInstance>(session.PCH); auto Clang = std::make_unique<CompilerInstance>(session.PCH);
@ -190,52 +204,53 @@ bool Parse(CompilerInstance &Clang) {
void CompletionPreloadMain(CompletionManager *manager) { void CompletionPreloadMain(CompletionManager *manager) {
while (true) { while (true) {
// Fetching the completion request blocks until we have a request.
auto request = manager->preload_requests_.Dequeue(); auto request = manager->preload_requests_.Dequeue();
// If we don't get a session then that means we don't care about the file bool is_open = false;
// anymore - abandon the request. std::shared_ptr<CompletionSession> session =
std::shared_ptr<CompletionSession> session = manager->TryGetSession( manager->TryGetSession(request.path, true, &is_open);
request.path, false /*mark_as_completion*/, false /*create_if_needed*/);
if (!session) if (!session)
continue; continue;
const auto &args = session->file.args; // For inferred session, don't build preamble because changes in a.h will
WorkingFiles::Snapshot snapshot = session->wfiles->AsSnapshot( // invalidate it.
{StripFileType(session->file.filename)}); if (!session->inferred) {
const auto &args = session->file.args;
LOG_S(INFO) << "create completion session for " << session->file.filename; WorkingFiles::Snapshot snapshot = session->wfiles->AsSnapshot(
if (std::unique_ptr<CompilerInvocation> CI = {StripFileType(session->file.filename)});
BuildCompilerInvocation(args, session->FS)) if (std::unique_ptr<CompilerInvocation> CI =
session->BuildPreamble(*CI); BuildCompilerInvocation(args, session->FS))
if (g_config->diagnostics.onSave) { session->BuildPreamble(*CI, request.path);
}
int debounce =
is_open ? g_config->diagnostics.onOpen : g_config->diagnostics.onSave;
if (debounce >= 0) {
lsTextDocumentIdentifier document; lsTextDocumentIdentifier document;
document.uri = lsDocumentUri::FromPath(request.path); document.uri = lsDocumentUri::FromPath(request.path);
manager->diagnostic_request_.PushBack({document}, true); manager->DiagnosticsUpdate(request.path, debounce);
} }
} }
} }
void CompletionMain(CompletionManager *completion_manager) { void CompletionMain(CompletionManager *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.
std::unique_ptr<CompletionManager::CompletionRequest> request = std::unique_ptr<CompletionManager::CompletionRequest> request =
completion_manager->completion_request_.Dequeue(); manager->completion_request_.Dequeue();
// Drop older requests if we're not buffering. // Drop older requests if we're not buffering.
while (g_config->completion.dropOldRequests && while (g_config->completion.dropOldRequests &&
!completion_manager->completion_request_.IsEmpty()) { !manager->completion_request_.IsEmpty()) {
completion_manager->on_dropped_(request->id); manager->on_dropped_(request->id);
request->Consumer.reset(); request->Consumer.reset();
request->on_complete(nullptr); request->on_complete(nullptr);
request = completion_manager->completion_request_.Dequeue(); request = manager->completion_request_.Dequeue();
} }
std::string path = request->document.uri.GetPath(); std::string path = request->document.uri.GetPath();
std::shared_ptr<CompletionSession> session = std::shared_ptr<CompletionSession> session =
completion_manager->TryGetSession(path, true /*mark_as_completion*/, manager->TryGetSession(path, false);
true /*create_if_needed*/);
std::unique_ptr<CompilerInvocation> CI = std::unique_ptr<CompilerInvocation> CI =
BuildCompilerInvocation(session->file.args, session->FS); BuildCompilerInvocation(session->file.args, session->FS);
@ -251,7 +266,7 @@ void CompletionMain(CompletionManager *completion_manager) {
DiagnosticConsumer DC; DiagnosticConsumer DC;
WorkingFiles::Snapshot snapshot = WorkingFiles::Snapshot snapshot =
completion_manager->working_files_->AsSnapshot({StripFileType(path)}); manager->working_files_->AsSnapshot({StripFileType(path)});
std::vector<std::unique_ptr<llvm::MemoryBuffer>> Bufs; std::vector<std::unique_ptr<llvm::MemoryBuffer>> Bufs;
auto Clang = BuildCompilerInstance(*session, std::move(CI), DC, snapshot, Bufs); auto Clang = BuildCompilerInstance(*session, std::move(CI), DC, snapshot, Bufs);
if (!Clang) if (!Clang)
@ -285,25 +300,31 @@ llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) {
} }
void printDiag(llvm::raw_string_ostream &OS, const DiagBase &d) { void printDiag(llvm::raw_string_ostream &OS, const DiagBase &d) {
if (d.inside_main) if (d.concerned)
OS << llvm::sys::path::filename(d.file); OS << llvm::sys::path::filename(d.file);
else else
OS << d.file; OS << d.file;
auto pos = d.range.start; auto pos = d.range.start;
OS << ":" << (pos.line + 1) << ":" << (pos.column + 1) << ":" OS << ":" << (pos.line + 1) << ":" << (pos.column + 1) << ":"
<< (d.inside_main ? " " : "\n"); << (d.concerned ? " " : "\n");
OS << diagLeveltoString(d.level) << ": " << d.message; OS << diagLeveltoString(d.level) << ": " << d.message;
} }
void DiagnosticMain(CompletionManager *manager) { void DiagnosticMain(CompletionManager *manager) {
while (true) { while (true) {
// Fetching the completion request blocks until we have a request.
CompletionManager::DiagnosticRequest request = CompletionManager::DiagnosticRequest request =
manager->diagnostic_request_.Dequeue(); manager->diagnostic_request_.Dequeue();
std::string path = request.document.uri.GetPath(); const std::string &path = request.path;
int64_t wait = request.wait_until -
chrono::duration_cast<chrono::milliseconds>(
chrono::high_resolution_clock::now().time_since_epoch())
.count();
if (wait > 0)
std::this_thread::sleep_for(chrono::duration<int64_t, std::milli>(
std::min(wait, request.debounce)));
std::shared_ptr<CompletionSession> session = manager->TryGetSession( std::shared_ptr<CompletionSession> session =
path, true /*mark_as_completion*/, true /*create_if_needed*/); manager->TryGetSession(path, false);
std::unique_ptr<CompilerInvocation> CI = std::unique_ptr<CompilerInvocation> CI =
BuildCompilerInvocation(session->file.args, session->FS); BuildCompilerInvocation(session->file.args, session->FS);
@ -311,7 +332,7 @@ void DiagnosticMain(CompletionManager *manager) {
continue; continue;
CI->getDiagnosticOpts().IgnoreWarnings = false; CI->getDiagnosticOpts().IgnoreWarnings = false;
CI->getLangOpts()->SpellChecking = g_config->diagnostics.spellChecking; CI->getLangOpts()->SpellChecking = g_config->diagnostics.spellChecking;
StoreDiags DC; StoreDiags DC(path);
WorkingFiles::Snapshot snapshot = WorkingFiles::Snapshot snapshot =
manager->working_files_->AsSnapshot({StripFileType(path)}); manager->working_files_->AsSnapshot({StripFileType(path)});
std::vector<std::unique_ptr<llvm::MemoryBuffer>> Bufs; std::vector<std::unique_ptr<llvm::MemoryBuffer>> Bufs;
@ -347,9 +368,12 @@ void DiagnosticMain(CompletionManager *manager) {
return ret; return ret;
}; };
std::vector<Diag> diags = DC.Take();
if (std::shared_ptr<PreambleData> preamble = session->GetPreamble())
diags.insert(diags.end(), preamble->diags.begin(), preamble->diags.end());
std::vector<lsDiagnostic> ls_diags; std::vector<lsDiagnostic> ls_diags;
for (auto &d : DC.Take()) { for (auto &d : diags) {
if (!d.inside_main) if (!d.concerned)
continue; continue;
std::string buf; std::string buf;
llvm::raw_string_ostream OS(buf); llvm::raw_string_ostream OS(buf);
@ -364,7 +388,7 @@ void DiagnosticMain(CompletionManager *manager) {
OS.flush(); OS.flush();
ls_diag.message = std::move(buf); ls_diag.message = std::move(buf);
for (auto &n : d.notes) { for (auto &n : d.notes) {
if (!n.inside_main) if (!n.concerned)
continue; continue;
lsDiagnostic &ls_diag1 = ls_diags.emplace_back(); lsDiagnostic &ls_diag1 = ls_diags.emplace_back();
Fill(n, ls_diag1); Fill(n, ls_diag1);
@ -374,6 +398,12 @@ void DiagnosticMain(CompletionManager *manager) {
ls_diag1.message = std::move(buf); ls_diag1.message = std::move(buf);
} }
} }
{
std::lock_guard lock(session->wfiles->files_mutex);
if (WorkingFile *wfile = session->wfiles->GetFileByFilenameNoLock(path))
wfile->diagnostics_ = ls_diags;
}
manager->on_diagnostic_(path, ls_diags); manager->on_diagnostic_(path, ls_diags);
} }
} }
@ -385,9 +415,10 @@ std::shared_ptr<PreambleData> CompletionSession::GetPreamble() {
return preamble; return preamble;
} }
void CompletionSession::BuildPreamble(CompilerInvocation &CI) { void CompletionSession::BuildPreamble(CompilerInvocation &CI,
const std::string &main) {
std::shared_ptr<PreambleData> OldP = GetPreamble(); std::shared_ptr<PreambleData> OldP = GetPreamble();
std::string content = wfiles->GetContent(file.filename); std::string content = wfiles->GetContent(main);
std::unique_ptr<llvm::MemoryBuffer> Buf = std::unique_ptr<llvm::MemoryBuffer> Buf =
llvm::MemoryBuffer::getMemBuffer(content); llvm::MemoryBuffer::getMemBuffer(content);
auto Bounds = ComputePreambleBounds(*CI.getLangOpts(), Buf.get(), 0); auto Bounds = ComputePreambleBounds(*CI.getLangOpts(), Buf.get(), 0);
@ -401,7 +432,7 @@ void CompletionSession::BuildPreamble(CompilerInvocation &CI) {
CI.getPreprocessorOpts().WriteCommentListToPCH = false; CI.getPreprocessorOpts().WriteCommentListToPCH = false;
#endif #endif
StoreDiags DC; StoreDiags DC(main);
IntrusiveRefCntPtr<DiagnosticsEngine> DE = IntrusiveRefCntPtr<DiagnosticsEngine> DE =
CompilerInstance::createDiagnostics(&CI.getDiagnosticOpts(), &DC, false); CompilerInstance::createDiagnostics(&CI.getDiagnosticOpts(), &DC, false);
PreambleCallbacks PP; PreambleCallbacks PP;
@ -421,8 +452,8 @@ CompletionManager::CompletionManager(Project *project,
OnDropped on_dropped) OnDropped on_dropped)
: project_(project), working_files_(working_files), : project_(project), working_files_(working_files),
on_diagnostic_(on_diagnostic), on_dropped_(on_dropped), on_diagnostic_(on_diagnostic), on_dropped_(on_dropped),
preloaded_sessions_(kMaxPreloadedSessions), preloads(kMaxPreloadedSessions),
completion_sessions_(kMaxCompletionSessions), sessions(kMaxCompletionSessions),
PCH(std::make_shared<PCHContainerOperations>()) { PCH(std::make_shared<PCHContainerOperations>()) {
std::thread([&]() { std::thread([&]() {
set_thread_name("comp"); set_thread_name("comp");
@ -441,22 +472,28 @@ CompletionManager::CompletionManager(Project *project,
.detach(); .detach();
} }
void CompletionManager::CodeComplete( void CompletionManager::DiagnosticsUpdate(const std::string &path,
const lsRequestId &id, int debounce) {
const lsTextDocumentPositionParams &completion_location, static GroupMatch match(g_config->diagnostics.whitelist,
const OnComplete &on_complete) { g_config->diagnostics.blacklist);
} if (!match.IsMatch(path))
return;
void CompletionManager::DiagnosticsUpdate( int64_t now = chrono::duration_cast<chrono::milliseconds>(
const lsTextDocumentIdentifier &document) { chrono::high_resolution_clock::now().time_since_epoch())
bool has = false; .count();
diagnostic_request_.Iterate([&](const DiagnosticRequest &request) { bool flag = false;
if (request.document.uri == document.uri) {
has = true; std::lock_guard lock(diag_mutex);
}); int64_t &next = next_diag[path];
if (!has) auto &d = g_config->diagnostics;
diagnostic_request_.PushBack(DiagnosticRequest{document}, if (next <= now ||
true /*priority*/); now - next > std::max(d.onChange, std::max(d.onChange, d.onSave))) {
next = now + debounce;
flag = true;
}
}
if (flag)
diagnostic_request_.PushBack({path, now + debounce, debounce}, false);
} }
void CompletionManager::NotifyView(const std::string &path) { void CompletionManager::NotifyView(const std::string &path) {
@ -466,94 +503,67 @@ void CompletionManager::NotifyView(const std::string &path) {
} }
void CompletionManager::NotifySave(const std::string &filename) { void CompletionManager::NotifySave(const std::string &filename) {
//
// On save, always reparse.
//
EnsureCompletionOrCreatePreloadSession(filename); EnsureCompletionOrCreatePreloadSession(filename);
preload_requests_.PushBack(PreloadRequest{filename}, true); preload_requests_.PushBack(PreloadRequest{filename}, true);
} }
void CompletionManager::NotifyClose(const std::string &filename) { void CompletionManager::OnClose(const std::string &filename) {
//
// On close, we clear any existing CompletionSession instance.
//
std::lock_guard<std::mutex> lock(sessions_lock_); std::lock_guard<std::mutex> lock(sessions_lock_);
preloads.TryTake(filename);
// Take and drop. It's okay if we don't actually drop the file, it'll sessions.TryTake(filename);
// eventually get pushed out of the caches as the user opens other files.
auto preloaded_ptr = preloaded_sessions_.TryTake(filename);
LOG_IF_S(INFO, !!preloaded_ptr)
<< "Dropped preloaded-based code completion session for " << filename;
auto completion_ptr = completion_sessions_.TryTake(filename);
LOG_IF_S(INFO, !!completion_ptr)
<< "Dropped completion-based code completion session for " << filename;
// We should never have both a preloaded and completion session.
assert((preloaded_ptr && completion_ptr) == false);
} }
bool CompletionManager::EnsureCompletionOrCreatePreloadSession( bool CompletionManager::EnsureCompletionOrCreatePreloadSession(
const std::string &path) { const std::string &path) {
std::lock_guard<std::mutex> lock(sessions_lock_); std::lock_guard<std::mutex> lock(sessions_lock_);
if (preloads.TryGet(path) || sessions.TryGet(path))
// Check for an existing CompletionSession.
if (preloaded_sessions_.TryGet(path) ||
completion_sessions_.TryGet(path)) {
return false; return false;
}
// No CompletionSession, create new one. // No CompletionSession, create new one.
auto session = std::make_shared<ccls::CompletionSession>( auto session = std::make_shared<ccls::CompletionSession>(
project_->FindCompilationEntryForFile(path), working_files_, PCH); project_->FindCompilationEntryForFile(path), working_files_, PCH);
preloaded_sessions_.Insert(session->file.filename, session); if (session->file.filename != path) {
session->inferred = true;
session->file.filename = path;
}
preloads.Insert(path, session);
LOG_S(INFO) << "create preload session for " << path;
return true; return true;
} }
std::shared_ptr<ccls::CompletionSession> std::shared_ptr<ccls::CompletionSession>
CompletionManager::TryGetSession(const std::string &path, CompletionManager::TryGetSession(const std::string &path, bool preload,
bool mark_as_completion, bool *is_open) {
bool create_if_needed) {
std::lock_guard<std::mutex> lock(sessions_lock_); std::lock_guard<std::mutex> lock(sessions_lock_);
std::shared_ptr<ccls::CompletionSession> session = preloads.TryGet(path);
// Try to find a preloaded session. if (session) {
std::shared_ptr<ccls::CompletionSession> preloaded = if (!preload) {
preloaded_sessions_.TryGet(path); preloads.TryTake(path);
sessions.Insert(path, session);
if (preloaded) { if (is_open)
// If this request is for a completion, we should move it to *is_open = true;
// |completion_sessions|.
if (mark_as_completion) {
preloaded_sessions_.TryTake(path);
completion_sessions_.Insert(path, preloaded);
} }
return preloaded; return session;
} }
// Try to find a completion session. If none create one. session = sessions.TryGet(path);
std::shared_ptr<ccls::CompletionSession> session = if (!session && !preload) {
completion_sessions_.TryGet(path);
if (!session && create_if_needed) {
session = std::make_shared<ccls::CompletionSession>( session = std::make_shared<ccls::CompletionSession>(
project_->FindCompilationEntryForFile(path), working_files_, PCH); project_->FindCompilationEntryForFile(path), working_files_, PCH);
completion_sessions_.Insert(path, session); sessions.Insert(path, session);
LOG_S(INFO) << "create session for " << path;
if (is_open)
*is_open = true;
} }
return session; return session;
} }
void CompletionManager::FlushSession(const std::string &path) {
std::lock_guard<std::mutex> lock(sessions_lock_);
preloaded_sessions_.TryTake(path);
completion_sessions_.TryTake(path);
}
void CompletionManager::FlushAllSessions() { void CompletionManager::FlushAllSessions() {
LOG_S(INFO) << "flush all clang complete sessions"; LOG_S(INFO) << "flush all clang complete sessions";
std::lock_guard<std::mutex> lock(sessions_lock_); std::lock_guard<std::mutex> lock(sessions_lock_);
preloaded_sessions_.Clear(); preloads.Clear();
completion_sessions_.Clear(); sessions.Clear();
} }

View File

@ -27,7 +27,7 @@ struct DiagBase {
std::string file; std::string file;
clang::DiagnosticsEngine::Level level = clang::DiagnosticsEngine::Note; clang::DiagnosticsEngine::Level level = clang::DiagnosticsEngine::Note;
unsigned category; unsigned category;
bool inside_main = false; bool concerned = false;
}; };
struct Note : DiagBase {}; struct Note : DiagBase {};
struct Diag : DiagBase { struct Diag : DiagBase {
@ -46,10 +46,10 @@ struct CompletionSession
: public std::enable_shared_from_this<CompletionSession> { : public std::enable_shared_from_this<CompletionSession> {
std::mutex mutex; std::mutex mutex;
std::shared_ptr<PreambleData> preamble; std::shared_ptr<PreambleData> preamble;
std::vector<Diag> diags;
Project::Entry file; Project::Entry file;
WorkingFiles *wfiles; WorkingFiles *wfiles;
bool inferred = false;
// TODO share // TODO share
llvm::IntrusiveRefCntPtr<clang::vfs::FileSystem> FS = llvm::IntrusiveRefCntPtr<clang::vfs::FileSystem> FS =
@ -61,7 +61,7 @@ struct CompletionSession
: file(file), wfiles(wfiles), PCH(PCH) {} : file(file), wfiles(wfiles), PCH(PCH) {}
std::shared_ptr<PreambleData> GetPreamble(); std::shared_ptr<PreambleData> GetPreamble();
void BuildPreamble(clang::CompilerInvocation &CI); void BuildPreamble(clang::CompilerInvocation &CI, const std::string &main);
}; };
} }
@ -95,19 +95,16 @@ struct CompletionManager {
OnComplete on_complete; OnComplete on_complete;
}; };
struct DiagnosticRequest { struct DiagnosticRequest {
lsTextDocumentIdentifier document; std::string path;
int64_t wait_until;
int64_t debounce;
}; };
CompletionManager(Project *project, WorkingFiles *working_files, CompletionManager(Project *project, WorkingFiles *working_files,
OnDiagnostic on_diagnostic, OnDropped on_dropped); OnDiagnostic on_diagnostic, OnDropped on_dropped);
// Start a code completion at the given location. |on_complete| will run when
// completion results are available. |on_complete| may run on any thread.
void CodeComplete(const lsRequestId &request_id,
const lsTextDocumentPositionParams &completion_location,
const OnComplete &on_complete);
// Request a diagnostics update. // Request a diagnostics update.
void DiagnosticsUpdate(const lsTextDocumentIdentifier &document); void DiagnosticsUpdate(const std::string &path, int debounce);
// Notify the completion manager that |filename| has been viewed and we // Notify the completion manager that |filename| has been viewed and we
// should begin preloading completion data. // should begin preloading completion data.
@ -117,7 +114,7 @@ struct CompletionManager {
void NotifySave(const std::string &path); void NotifySave(const std::string &path);
// Notify the completion manager that |filename| has been closed. Any existing // Notify the completion manager that |filename| has been closed. Any existing
// completion session will be dropped. // completion session will be dropped.
void NotifyClose(const std::string &path); void OnClose(const std::string &path);
// Ensures there is a completion or preloaded session. Returns true if a new // Ensures there is a completion or preloaded session. Returns true if a new
// session was created. // session was created.
@ -125,11 +122,8 @@ struct CompletionManager {
// Tries to find an edit session for |filename|. This will move the session // Tries to find an edit session for |filename|. This will move the session
// from view to edit. // from view to edit.
std::shared_ptr<ccls::CompletionSession> std::shared_ptr<ccls::CompletionSession>
TryGetSession(const std::string &path, bool mark_as_completion, TryGetSession(const std::string &path, bool preload, bool *is_open = nullptr);
bool create_if_needed);
// Flushes all saved sessions with the supplied filename
void FlushSession(const std::string &path);
// Flushes all saved sessions // Flushes all saved sessions
void FlushAllSessions(void); void FlushAllSessions(void);
@ -147,14 +141,17 @@ struct CompletionManager {
// CompletionSession instances which are preloaded, ie, files which the user // CompletionSession instances which are preloaded, ie, files which the user
// has viewed but not requested code completion for. // has viewed but not requested code completion for.
LruSessionCache preloaded_sessions_; LruSessionCache preloads;
// CompletionSession instances which the user has actually performed // CompletionSession instances which the user has actually performed
// completion on. This is more rare so these instances tend to stay alive // completion on. This is more rare so these instances tend to stay alive
// much longer than the ones in |preloaded_sessions_|. // much longer than the ones in |preloaded_sessions_|.
LruSessionCache completion_sessions_; LruSessionCache sessions;
// Mutex which protects |view_sessions_| and |edit_sessions_|. // Mutex which protects |view_sessions_| and |edit_sessions_|.
std::mutex sessions_lock_; std::mutex sessions_lock_;
std::mutex diag_mutex;
std::unordered_map<std::string, int64_t> next_diag;
// 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_; ThreadedQueue<DiagnosticRequest> diagnostic_request_;

View File

@ -144,20 +144,18 @@ struct Config {
// blacklisted files. // blacklisted files.
std::vector<std::string> blacklist; std::vector<std::string> blacklist;
// How often should ccls publish diagnostics in completion? // Time to wait before computing diagnostics for textDocument/didChange.
// -1: never // -1: disable diagnostics on change
// 0: as often as possible // 0: immediately
// xxx: at most every xxx milliseconds // positive (e.g. 500): wait for 500 milliseconds. didChange requests in
int frequencyMs = 0; // this period of time will only cause one computation.
int onChange = 1000;
// If true, diagnostics will be reported for textDocument/didChange. // Time to wait before computing diagnostics for textDocument/didOpen.
bool onChange = true; int onOpen = 0;
// If true, diagnostics will be reported for textDocument/didOpen. // Time to wait before computing diagnostics for textDocument/didSave.
bool onOpen = true; int onSave = 0;
// If true, diagnostics will be reported for textDocument/didSave.
bool onSave = true;
bool spellChecking = true; bool spellChecking = true;
@ -246,8 +244,8 @@ MAKE_REFLECT_STRUCT(Config::Completion, caseSensitivity, detailedLabel,
dropOldRequests, duplicateOptional, filterAndSort, dropOldRequests, duplicateOptional, filterAndSort,
includeBlacklist, includeMaxPathSize, includeBlacklist, includeMaxPathSize,
includeSuffixWhitelist, includeWhitelist); includeSuffixWhitelist, includeWhitelist);
MAKE_REFLECT_STRUCT(Config::Diagnostics, blacklist, frequencyMs, onChange, MAKE_REFLECT_STRUCT(Config::Diagnostics, blacklist, onChange, onOpen, onSave,
onOpen, onSave, spellChecking, whitelist) spellChecking, whitelist)
MAKE_REFLECT_STRUCT(Config::Highlight, lsRanges, blacklist, whitelist) MAKE_REFLECT_STRUCT(Config::Highlight, lsRanges, blacklist, whitelist)
MAKE_REFLECT_STRUCT(Config::Index, blacklist, comments, enabled, multiVersion, MAKE_REFLECT_STRUCT(Config::Index, blacklist, comments, enabled, multiVersion,
multiVersionBlacklist, multiVersionWhitelist, onChange, multiVersionBlacklist, multiVersionWhitelist, onChange,

View File

@ -15,7 +15,6 @@
struct CompletionManager; struct CompletionManager;
struct Config; struct Config;
class DiagnosticsPublisher;
struct GroupMatch; struct GroupMatch;
struct VFS; struct VFS;
struct IncludeComplete; struct IncludeComplete;
@ -78,7 +77,6 @@ struct MessageHandler {
DB *db = nullptr; DB *db = nullptr;
MultiQueueWaiter *waiter = nullptr; MultiQueueWaiter *waiter = nullptr;
Project *project = nullptr; Project *project = nullptr;
DiagnosticsPublisher *diag_pub = nullptr;
VFS *vfs = nullptr; VFS *vfs = nullptr;
SemanticHighlight *highlight = nullptr; SemanticHighlight *highlight = nullptr;
WorkingFiles *working_files = nullptr; WorkingFiles *working_files = nullptr;

View File

@ -1,6 +1,7 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "clang_complete.hh"
#include "match.h" #include "match.h"
#include "message_handler.h" #include "message_handler.h"
#include "pipeline.hh" #include "pipeline.hh"
@ -38,6 +39,7 @@ struct Handler_CclsReload : BaseMessageHandler<In_CclsReload> {
vfs->Clear(); vfs->Clear();
db->clear(); db->clear();
project->Index(working_files, lsRequestId()); project->Index(working_files, lsRequestId());
clang_complete->FlushAllSessions();
return; return;
} }

View File

@ -474,7 +474,6 @@ struct Handler_Initialize : BaseMessageHandler<In_InitializeRequest> {
sys::fs::create_directories(g_config->cacheDirectory + '@' + escaped); sys::fs::create_directories(g_config->cacheDirectory + '@' + escaped);
} }
diag_pub->Init();
idx::Init(); idx::Init();
highlight->Init(); highlight->Init();

View File

@ -30,9 +30,8 @@ struct Handler_TextDocumentDidChange
if (g_config->index.onChange) if (g_config->index.onChange)
pipeline::Index(path, {}, IndexMode::OnChange); pipeline::Index(path, {}, IndexMode::OnChange);
clang_complete->NotifyView(path); clang_complete->NotifyView(path);
if (g_config->diagnostics.onChange) if (g_config->diagnostics.onChange >= 0)
clang_complete->DiagnosticsUpdate( clang_complete->DiagnosticsUpdate(path, g_config->diagnostics.onChange);
params.textDocument.AsTextDocumentIdentifier());
} }
}; };
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidChange); REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidChange);

View File

@ -35,7 +35,7 @@ struct Handler_TextDocumentDidClose
// Remove internal state. // Remove internal state.
working_files->OnClose(request->params.textDocument); working_files->OnClose(request->params.textDocument);
clang_complete->NotifyClose(path); clang_complete->OnClose(path);
} }
}; };
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidClose); REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidClose);

View File

@ -59,14 +59,10 @@ struct Handler_TextDocumentDidOpen
project->SetArgsForFile(args, path); project->SetArgsForFile(args, path);
// Submit new index request if it is not a header file. // Submit new index request if it is not a header file.
if (SourceFileLanguage(path) != LanguageId::Unknown) { if (SourceFileLanguage(path) != LanguageId::Unknown)
pipeline::Index(path, args, IndexMode::Normal); pipeline::Index(path, args, IndexMode::Normal);
clang_complete->FlushSession(path);
}
clang_complete->NotifyView(path); clang_complete->NotifyView(path);
if (g_config->diagnostics.onOpen)
clang_complete->DiagnosticsUpdate({params.textDocument.uri});
} }
}; };
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidOpen); REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidOpen);

View File

@ -52,12 +52,12 @@ struct Handler_WorkspaceDidChangeWatchedFiles
if (mode == IndexMode::Normal) if (mode == IndexMode::Normal)
clang_complete->NotifySave(path); clang_complete->NotifySave(path);
else else
clang_complete->FlushSession(path); clang_complete->OnClose(path);
break; break;
} }
case lsFileChangeType::Deleted: case lsFileChangeType::Deleted:
pipeline::Index(path, {}, mode); pipeline::Index(path, {}, mode);
clang_complete->FlushSession(path); clang_complete->OnClose(path);
break; break;
} }
} }

View File

@ -27,41 +27,6 @@ using namespace llvm;
#include <unistd.h> #include <unistd.h>
#endif #endif
void DiagnosticsPublisher::Init() {
frequencyMs_ = g_config->diagnostics.frequencyMs;
match_ = std::make_unique<GroupMatch>(g_config->diagnostics.whitelist,
g_config->diagnostics.blacklist);
}
void DiagnosticsPublisher::Publish(WorkingFiles *working_files,
std::string path,
std::vector<lsDiagnostic> diagnostics) {
bool good = true;
// Cache diagnostics so we can show fixits.
working_files->DoActionOnFile(path, [&](WorkingFile *working_file) {
if (working_file) {
good = working_file->diagnostics_.empty();
working_file->diagnostics_ = diagnostics;
}
});
int64_t now =
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count();
if (frequencyMs_ >= 0 &&
(nextPublish_ <= now || (!good && diagnostics.empty())) &&
match_->IsMatch(path)) {
nextPublish_ = now + frequencyMs_;
Out_TextDocumentPublishDiagnostics out;
out.params.uri = lsDocumentUri::FromPath(path);
out.params.diagnostics = diagnostics;
ccls::pipeline::WriteStdout(kMethodType_TextDocumentPublishDiagnostics,
out);
}
}
void VFS::Clear() { void VFS::Clear() {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
state.clear(); state.clear();
@ -485,12 +450,15 @@ void MainLoop() {
SemanticHighlight highlight; SemanticHighlight highlight;
WorkingFiles working_files; WorkingFiles working_files;
VFS vfs; VFS vfs;
DiagnosticsPublisher diag_pub;
CompletionManager clang_complete( CompletionManager clang_complete(
&project, &working_files, &project, &working_files,
[&](std::string path, std::vector<lsDiagnostic> diagnostics) { [&](std::string path, std::vector<lsDiagnostic> diagnostics) {
diag_pub.Publish(&working_files, path, diagnostics); Out_TextDocumentPublishDiagnostics out;
out.params.uri = lsDocumentUri::FromPath(path);
out.params.diagnostics = diagnostics;
ccls::pipeline::WriteStdout(kMethodType_TextDocumentPublishDiagnostics,
out);
}, },
[](lsRequestId id) { [](lsRequestId id) {
if (id.Valid()) { if (id.Valid()) {
@ -511,7 +479,6 @@ void MainLoop() {
handler->db = &db; handler->db = &db;
handler->waiter = indexer_waiter; handler->waiter = indexer_waiter;
handler->project = &project; handler->project = &project;
handler->diag_pub = &diag_pub;
handler->vfs = &vfs; handler->vfs = &vfs;
handler->highlight = &highlight; handler->highlight = &highlight;
handler->working_files = &working_files; handler->working_files = &working_files;

View File

@ -19,18 +19,6 @@ struct Project;
struct WorkingFiles; struct WorkingFiles;
struct lsBaseOutMessage; struct lsBaseOutMessage;
class DiagnosticsPublisher {
std::unique_ptr<GroupMatch> match_;
int64_t nextPublish_ = 0;
int frequencyMs_;
public:
void Init();
void Publish(WorkingFiles* working_files,
std::string path,
std::vector<lsDiagnostic> diagnostics);
};
struct VFS { struct VFS {
struct State { struct State {
int64_t timestamp; int64_t timestamp;

View File

@ -54,15 +54,6 @@ enum OptionClass {
Separate, Separate,
}; };
std::string ResolveIfRelative(const std::string &directory,
const std::string &path) {
if (sys::path::is_absolute(path))
return path;
SmallString<256> Ret;
sys::path::append(Ret, directory, path);
return NormalizePath(Ret.str());
}
struct ProjectProcessor { struct ProjectProcessor {
ProjectConfig *config; ProjectConfig *config;
std::unordered_set<size_t> command_set; std::unordered_set<size_t> command_set;

View File

@ -105,6 +105,15 @@ std::string EscapeFileName(std::string path) {
return path; return path;
} }
std::string ResolveIfRelative(const std::string &directory,
const std::string &path) {
if (sys::path::is_absolute(path))
return path;
SmallString<256> Ret;
sys::path::append(Ret, directory, path);
return NormalizePath(Ret.str());
}
std::optional<int64_t> LastWriteTime(const std::string &path) { std::optional<int64_t> LastWriteTime(const std::string &path) {
sys::fs::file_status Status; sys::fs::file_status Status;
if (sys::fs::status(path, Status)) if (sys::fs::status(path, Status))

View File

@ -58,6 +58,9 @@ void EnsureEndsInSlash(std::string &path);
// e.g. foo/bar.c => foo_bar.c // e.g. foo/bar.c => foo_bar.c
std::string EscapeFileName(std::string path); std::string EscapeFileName(std::string path);
std::string ResolveIfRelative(const std::string &directory,
const std::string &path);
std::optional<int64_t> LastWriteTime(const std::string &path); std::optional<int64_t> LastWriteTime(const std::string &path);
std::optional<std::string> ReadContent(const std::string &filename); std::optional<std::string> ReadContent(const std::string &filename);
void WriteToFile(const std::string &filename, const std::string &content); void WriteToFile(const std::string &filename, const std::string &content);