mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-30 11:27:07 +00:00
Publish diagnostics of inferred files and change diagnostics.{onChange,onOpen,onSave} from bool to debounce time
This commit is contained in:
parent
1a2e31660c
commit
4792ad845b
@ -6,6 +6,7 @@
|
||||
#include "clang_utils.h"
|
||||
#include "filesystem.hh"
|
||||
#include "log.hh"
|
||||
#include "match.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include <clang/Frontend/CompilerInstance.h>
|
||||
@ -20,7 +21,10 @@ using namespace clang;
|
||||
using namespace llvm;
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <ratio>
|
||||
#include <thread>
|
||||
namespace chrono = std::chrono;
|
||||
|
||||
namespace ccls {
|
||||
namespace {
|
||||
@ -68,22 +72,34 @@ class StoreDiags : public DiagnosticConsumer {
|
||||
const LangOptions *LangOpts;
|
||||
std::optional<Diag> last;
|
||||
std::vector<Diag> output;
|
||||
std::string path;
|
||||
std::unordered_map<unsigned, bool> FID2concerned;
|
||||
void Flush() {
|
||||
if (!last)
|
||||
return;
|
||||
bool mentions = last->inside_main || last->edits.size();
|
||||
bool mentions = last->concerned || last->edits.size();
|
||||
if (!mentions)
|
||||
for (auto &N : last->notes)
|
||||
if (N.inside_main)
|
||||
if (N.concerned)
|
||||
mentions = true;
|
||||
if (mentions)
|
||||
output.push_back(std::move(*last));
|
||||
last.reset();
|
||||
}
|
||||
public:
|
||||
StoreDiags(std::string path) : path(std::move(path)) {}
|
||||
std::vector<Diag> Take() {
|
||||
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 {
|
||||
LangOpts = &Opts;
|
||||
}
|
||||
@ -96,24 +112,25 @@ public:
|
||||
SourceLocation L = Info.getLocation();
|
||||
if (!L.isValid()) return;
|
||||
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) {
|
||||
llvm::SmallString<64> Message;
|
||||
Info.FormatDiagnostic(Message);
|
||||
d.range =
|
||||
FromCharSourceRange(SM, *LangOpts, DiagnosticRange(Info, *LangOpts));
|
||||
d.message = Message.str();
|
||||
d.inside_main = inside_main;
|
||||
d.file = SM.getFilename(Info.getLocation());
|
||||
d.concerned = concerned;
|
||||
d.file = Filename;
|
||||
d.level = Level;
|
||||
d.category = DiagnosticIDs::getCategoryNumberForDiag(Info.getID());
|
||||
};
|
||||
|
||||
auto addFix = [&](bool SyntheticMessage) -> bool {
|
||||
if (!inside_main)
|
||||
if (!concerned)
|
||||
return false;
|
||||
for (const FixItHint &FixIt : Info.getFixItHints()) {
|
||||
if (!SM.isInMainFile(FixIt.RemoveRange.getBegin()))
|
||||
if (!IsConcerned(SM, FixIt.RemoveRange.getBegin()))
|
||||
return false;
|
||||
lsTextEdit edit;
|
||||
edit.newText = FixIt.CodeToInsert;
|
||||
@ -146,9 +163,10 @@ std::unique_ptr<CompilerInstance> BuildCompilerInstance(
|
||||
CompletionSession &session, std::unique_ptr<CompilerInvocation> CI,
|
||||
DiagnosticConsumer &DC, const WorkingFiles::Snapshot &snapshot,
|
||||
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) {
|
||||
Bufs.push_back(llvm::MemoryBuffer::getMemBuffer(file.content));
|
||||
if (file.filename == session.file.filename) {
|
||||
if (file.filename == main)
|
||||
if (auto Preamble = session.GetPreamble()) {
|
||||
#if LLVM_VERSION_MAJOR >= 7
|
||||
Preamble->Preamble.OverridePreamble(*CI, session.FS,
|
||||
@ -157,14 +175,10 @@ std::unique_ptr<CompilerInstance> BuildCompilerInstance(
|
||||
Preamble->Preamble.AddImplicitPreamble(*CI, session.FS,
|
||||
Bufs.back().get());
|
||||
#endif
|
||||
} else {
|
||||
CI->getPreprocessorOpts().addRemappedFile(
|
||||
CI->getFrontendOpts().Inputs[0].getFile(), Bufs.back().get());
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
CI->getPreprocessorOpts().addRemappedFile(file.filename,
|
||||
Bufs.back().get());
|
||||
}
|
||||
CI->getPreprocessorOpts().addRemappedFile(file.filename,
|
||||
Bufs.back().get());
|
||||
}
|
||||
|
||||
auto Clang = std::make_unique<CompilerInstance>(session.PCH);
|
||||
@ -190,52 +204,53 @@ bool Parse(CompilerInstance &Clang) {
|
||||
|
||||
void CompletionPreloadMain(CompletionManager *manager) {
|
||||
while (true) {
|
||||
// Fetching the completion request blocks until we have a request.
|
||||
auto request = 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.
|
||||
std::shared_ptr<CompletionSession> session = manager->TryGetSession(
|
||||
request.path, false /*mark_as_completion*/, false /*create_if_needed*/);
|
||||
bool is_open = false;
|
||||
std::shared_ptr<CompletionSession> session =
|
||||
manager->TryGetSession(request.path, true, &is_open);
|
||||
if (!session)
|
||||
continue;
|
||||
|
||||
const auto &args = session->file.args;
|
||||
WorkingFiles::Snapshot snapshot = session->wfiles->AsSnapshot(
|
||||
{StripFileType(session->file.filename)});
|
||||
|
||||
LOG_S(INFO) << "create completion session for " << session->file.filename;
|
||||
if (std::unique_ptr<CompilerInvocation> CI =
|
||||
BuildCompilerInvocation(args, session->FS))
|
||||
session->BuildPreamble(*CI);
|
||||
if (g_config->diagnostics.onSave) {
|
||||
// For inferred session, don't build preamble because changes in a.h will
|
||||
// invalidate it.
|
||||
if (!session->inferred) {
|
||||
const auto &args = session->file.args;
|
||||
WorkingFiles::Snapshot snapshot = session->wfiles->AsSnapshot(
|
||||
{StripFileType(session->file.filename)});
|
||||
if (std::unique_ptr<CompilerInvocation> CI =
|
||||
BuildCompilerInvocation(args, session->FS))
|
||||
session->BuildPreamble(*CI, request.path);
|
||||
}
|
||||
int debounce =
|
||||
is_open ? g_config->diagnostics.onOpen : g_config->diagnostics.onSave;
|
||||
if (debounce >= 0) {
|
||||
lsTextDocumentIdentifier document;
|
||||
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) {
|
||||
// Fetching the completion request blocks until we have a request.
|
||||
std::unique_ptr<CompletionManager::CompletionRequest> request =
|
||||
completion_manager->completion_request_.Dequeue();
|
||||
manager->completion_request_.Dequeue();
|
||||
|
||||
// Drop older requests if we're not buffering.
|
||||
while (g_config->completion.dropOldRequests &&
|
||||
!completion_manager->completion_request_.IsEmpty()) {
|
||||
completion_manager->on_dropped_(request->id);
|
||||
!manager->completion_request_.IsEmpty()) {
|
||||
manager->on_dropped_(request->id);
|
||||
request->Consumer.reset();
|
||||
request->on_complete(nullptr);
|
||||
request = completion_manager->completion_request_.Dequeue();
|
||||
request = manager->completion_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*/);
|
||||
manager->TryGetSession(path, false);
|
||||
|
||||
std::unique_ptr<CompilerInvocation> CI =
|
||||
BuildCompilerInvocation(session->file.args, session->FS);
|
||||
@ -251,7 +266,7 @@ void CompletionMain(CompletionManager *completion_manager) {
|
||||
|
||||
DiagnosticConsumer DC;
|
||||
WorkingFiles::Snapshot snapshot =
|
||||
completion_manager->working_files_->AsSnapshot({StripFileType(path)});
|
||||
manager->working_files_->AsSnapshot({StripFileType(path)});
|
||||
std::vector<std::unique_ptr<llvm::MemoryBuffer>> Bufs;
|
||||
auto Clang = BuildCompilerInstance(*session, std::move(CI), DC, snapshot, Bufs);
|
||||
if (!Clang)
|
||||
@ -285,25 +300,31 @@ llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) {
|
||||
}
|
||||
|
||||
void printDiag(llvm::raw_string_ostream &OS, const DiagBase &d) {
|
||||
if (d.inside_main)
|
||||
if (d.concerned)
|
||||
OS << llvm::sys::path::filename(d.file);
|
||||
else
|
||||
OS << d.file;
|
||||
auto pos = d.range.start;
|
||||
OS << ":" << (pos.line + 1) << ":" << (pos.column + 1) << ":"
|
||||
<< (d.inside_main ? " " : "\n");
|
||||
<< (d.concerned ? " " : "\n");
|
||||
OS << diagLeveltoString(d.level) << ": " << d.message;
|
||||
}
|
||||
|
||||
void DiagnosticMain(CompletionManager *manager) {
|
||||
while (true) {
|
||||
// Fetching the completion request blocks until we have a request.
|
||||
CompletionManager::DiagnosticRequest request =
|
||||
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(
|
||||
path, true /*mark_as_completion*/, true /*create_if_needed*/);
|
||||
std::shared_ptr<CompletionSession> session =
|
||||
manager->TryGetSession(path, false);
|
||||
|
||||
std::unique_ptr<CompilerInvocation> CI =
|
||||
BuildCompilerInvocation(session->file.args, session->FS);
|
||||
@ -311,7 +332,7 @@ void DiagnosticMain(CompletionManager *manager) {
|
||||
continue;
|
||||
CI->getDiagnosticOpts().IgnoreWarnings = false;
|
||||
CI->getLangOpts()->SpellChecking = g_config->diagnostics.spellChecking;
|
||||
StoreDiags DC;
|
||||
StoreDiags DC(path);
|
||||
WorkingFiles::Snapshot snapshot =
|
||||
manager->working_files_->AsSnapshot({StripFileType(path)});
|
||||
std::vector<std::unique_ptr<llvm::MemoryBuffer>> Bufs;
|
||||
@ -347,9 +368,12 @@ void DiagnosticMain(CompletionManager *manager) {
|
||||
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;
|
||||
for (auto &d : DC.Take()) {
|
||||
if (!d.inside_main)
|
||||
for (auto &d : diags) {
|
||||
if (!d.concerned)
|
||||
continue;
|
||||
std::string buf;
|
||||
llvm::raw_string_ostream OS(buf);
|
||||
@ -364,7 +388,7 @@ void DiagnosticMain(CompletionManager *manager) {
|
||||
OS.flush();
|
||||
ls_diag.message = std::move(buf);
|
||||
for (auto &n : d.notes) {
|
||||
if (!n.inside_main)
|
||||
if (!n.concerned)
|
||||
continue;
|
||||
lsDiagnostic &ls_diag1 = ls_diags.emplace_back();
|
||||
Fill(n, ls_diag1);
|
||||
@ -374,6 +398,12 @@ void DiagnosticMain(CompletionManager *manager) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -385,9 +415,10 @@ std::shared_ptr<PreambleData> CompletionSession::GetPreamble() {
|
||||
return preamble;
|
||||
}
|
||||
|
||||
void CompletionSession::BuildPreamble(CompilerInvocation &CI) {
|
||||
void CompletionSession::BuildPreamble(CompilerInvocation &CI,
|
||||
const std::string &main) {
|
||||
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 =
|
||||
llvm::MemoryBuffer::getMemBuffer(content);
|
||||
auto Bounds = ComputePreambleBounds(*CI.getLangOpts(), Buf.get(), 0);
|
||||
@ -401,7 +432,7 @@ void CompletionSession::BuildPreamble(CompilerInvocation &CI) {
|
||||
CI.getPreprocessorOpts().WriteCommentListToPCH = false;
|
||||
#endif
|
||||
|
||||
StoreDiags DC;
|
||||
StoreDiags DC(main);
|
||||
IntrusiveRefCntPtr<DiagnosticsEngine> DE =
|
||||
CompilerInstance::createDiagnostics(&CI.getDiagnosticOpts(), &DC, false);
|
||||
PreambleCallbacks PP;
|
||||
@ -421,8 +452,8 @@ CompletionManager::CompletionManager(Project *project,
|
||||
OnDropped on_dropped)
|
||||
: project_(project), working_files_(working_files),
|
||||
on_diagnostic_(on_diagnostic), on_dropped_(on_dropped),
|
||||
preloaded_sessions_(kMaxPreloadedSessions),
|
||||
completion_sessions_(kMaxCompletionSessions),
|
||||
preloads(kMaxPreloadedSessions),
|
||||
sessions(kMaxCompletionSessions),
|
||||
PCH(std::make_shared<PCHContainerOperations>()) {
|
||||
std::thread([&]() {
|
||||
set_thread_name("comp");
|
||||
@ -441,22 +472,28 @@ CompletionManager::CompletionManager(Project *project,
|
||||
.detach();
|
||||
}
|
||||
|
||||
void CompletionManager::CodeComplete(
|
||||
const lsRequestId &id,
|
||||
const lsTextDocumentPositionParams &completion_location,
|
||||
const OnComplete &on_complete) {
|
||||
}
|
||||
|
||||
void CompletionManager::DiagnosticsUpdate(
|
||||
const lsTextDocumentIdentifier &document) {
|
||||
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 CompletionManager::DiagnosticsUpdate(const std::string &path,
|
||||
int debounce) {
|
||||
static GroupMatch match(g_config->diagnostics.whitelist,
|
||||
g_config->diagnostics.blacklist);
|
||||
if (!match.IsMatch(path))
|
||||
return;
|
||||
int64_t now = chrono::duration_cast<chrono::milliseconds>(
|
||||
chrono::high_resolution_clock::now().time_since_epoch())
|
||||
.count();
|
||||
bool flag = false;
|
||||
{
|
||||
std::lock_guard lock(diag_mutex);
|
||||
int64_t &next = next_diag[path];
|
||||
auto &d = g_config->diagnostics;
|
||||
if (next <= now ||
|
||||
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) {
|
||||
@ -466,94 +503,67 @@ void CompletionManager::NotifyView(const std::string &path) {
|
||||
}
|
||||
|
||||
void CompletionManager::NotifySave(const std::string &filename) {
|
||||
//
|
||||
// On save, always reparse.
|
||||
//
|
||||
|
||||
EnsureCompletionOrCreatePreloadSession(filename);
|
||||
preload_requests_.PushBack(PreloadRequest{filename}, true);
|
||||
}
|
||||
|
||||
void CompletionManager::NotifyClose(const std::string &filename) {
|
||||
//
|
||||
// On close, we clear any existing CompletionSession instance.
|
||||
//
|
||||
|
||||
void CompletionManager::OnClose(const std::string &filename) {
|
||||
std::lock_guard<std::mutex> lock(sessions_lock_);
|
||||
|
||||
// Take and drop. It's okay if we don't actually drop the file, it'll
|
||||
// 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);
|
||||
preloads.TryTake(filename);
|
||||
sessions.TryTake(filename);
|
||||
}
|
||||
|
||||
bool CompletionManager::EnsureCompletionOrCreatePreloadSession(
|
||||
const std::string &path) {
|
||||
std::lock_guard<std::mutex> lock(sessions_lock_);
|
||||
|
||||
// Check for an existing CompletionSession.
|
||||
if (preloaded_sessions_.TryGet(path) ||
|
||||
completion_sessions_.TryGet(path)) {
|
||||
if (preloads.TryGet(path) || sessions.TryGet(path))
|
||||
return false;
|
||||
}
|
||||
|
||||
// No CompletionSession, create new one.
|
||||
auto session = std::make_shared<ccls::CompletionSession>(
|
||||
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;
|
||||
}
|
||||
|
||||
std::shared_ptr<ccls::CompletionSession>
|
||||
CompletionManager::TryGetSession(const std::string &path,
|
||||
bool mark_as_completion,
|
||||
bool create_if_needed) {
|
||||
CompletionManager::TryGetSession(const std::string &path, bool preload,
|
||||
bool *is_open) {
|
||||
std::lock_guard<std::mutex> lock(sessions_lock_);
|
||||
std::shared_ptr<ccls::CompletionSession> session = preloads.TryGet(path);
|
||||
|
||||
// Try to find a preloaded session.
|
||||
std::shared_ptr<ccls::CompletionSession> preloaded =
|
||||
preloaded_sessions_.TryGet(path);
|
||||
|
||||
if (preloaded) {
|
||||
// If this request is for a completion, we should move it to
|
||||
// |completion_sessions|.
|
||||
if (mark_as_completion) {
|
||||
preloaded_sessions_.TryTake(path);
|
||||
completion_sessions_.Insert(path, preloaded);
|
||||
if (session) {
|
||||
if (!preload) {
|
||||
preloads.TryTake(path);
|
||||
sessions.Insert(path, session);
|
||||
if (is_open)
|
||||
*is_open = true;
|
||||
}
|
||||
return preloaded;
|
||||
return session;
|
||||
}
|
||||
|
||||
// Try to find a completion session. If none create one.
|
||||
std::shared_ptr<ccls::CompletionSession> session =
|
||||
completion_sessions_.TryGet(path);
|
||||
if (!session && create_if_needed) {
|
||||
session = sessions.TryGet(path);
|
||||
if (!session && !preload) {
|
||||
session = std::make_shared<ccls::CompletionSession>(
|
||||
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;
|
||||
}
|
||||
|
||||
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() {
|
||||
LOG_S(INFO) << "flush all clang complete sessions";
|
||||
std::lock_guard<std::mutex> lock(sessions_lock_);
|
||||
|
||||
preloaded_sessions_.Clear();
|
||||
completion_sessions_.Clear();
|
||||
preloads.Clear();
|
||||
sessions.Clear();
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ struct DiagBase {
|
||||
std::string file;
|
||||
clang::DiagnosticsEngine::Level level = clang::DiagnosticsEngine::Note;
|
||||
unsigned category;
|
||||
bool inside_main = false;
|
||||
bool concerned = false;
|
||||
};
|
||||
struct Note : DiagBase {};
|
||||
struct Diag : DiagBase {
|
||||
@ -46,10 +46,10 @@ struct CompletionSession
|
||||
: public std::enable_shared_from_this<CompletionSession> {
|
||||
std::mutex mutex;
|
||||
std::shared_ptr<PreambleData> preamble;
|
||||
std::vector<Diag> diags;
|
||||
|
||||
Project::Entry file;
|
||||
WorkingFiles *wfiles;
|
||||
bool inferred = false;
|
||||
|
||||
// TODO share
|
||||
llvm::IntrusiveRefCntPtr<clang::vfs::FileSystem> FS =
|
||||
@ -61,7 +61,7 @@ struct CompletionSession
|
||||
: file(file), wfiles(wfiles), PCH(PCH) {}
|
||||
|
||||
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;
|
||||
};
|
||||
struct DiagnosticRequest {
|
||||
lsTextDocumentIdentifier document;
|
||||
std::string path;
|
||||
int64_t wait_until;
|
||||
int64_t debounce;
|
||||
};
|
||||
|
||||
CompletionManager(Project *project, WorkingFiles *working_files,
|
||||
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.
|
||||
void DiagnosticsUpdate(const lsTextDocumentIdentifier &document);
|
||||
void DiagnosticsUpdate(const std::string &path, int debounce);
|
||||
|
||||
// Notify the completion manager that |filename| has been viewed and we
|
||||
// should begin preloading completion data.
|
||||
@ -117,7 +114,7 @@ struct CompletionManager {
|
||||
void NotifySave(const std::string &path);
|
||||
// Notify the completion manager that |filename| has been closed. Any existing
|
||||
// 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
|
||||
// session was created.
|
||||
@ -125,11 +122,8 @@ struct CompletionManager {
|
||||
// Tries to find an edit session for |filename|. This will move the session
|
||||
// from view to edit.
|
||||
std::shared_ptr<ccls::CompletionSession>
|
||||
TryGetSession(const std::string &path, bool mark_as_completion,
|
||||
bool create_if_needed);
|
||||
TryGetSession(const std::string &path, bool preload, bool *is_open = nullptr);
|
||||
|
||||
// Flushes all saved sessions with the supplied filename
|
||||
void FlushSession(const std::string &path);
|
||||
// Flushes all saved sessions
|
||||
void FlushAllSessions(void);
|
||||
|
||||
@ -147,14 +141,17 @@ struct CompletionManager {
|
||||
|
||||
// CompletionSession instances which are preloaded, ie, files which the user
|
||||
// has viewed but not requested code completion for.
|
||||
LruSessionCache preloaded_sessions_;
|
||||
LruSessionCache preloads;
|
||||
// CompletionSession instances which the user has actually performed
|
||||
// completion on. This is more rare so these instances tend to stay alive
|
||||
// much longer than the ones in |preloaded_sessions_|.
|
||||
LruSessionCache completion_sessions_;
|
||||
LruSessionCache sessions;
|
||||
// Mutex which protects |view_sessions_| and |edit_sessions_|.
|
||||
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.
|
||||
ThreadedQueue<std::unique_ptr<CompletionRequest>> completion_request_;
|
||||
ThreadedQueue<DiagnosticRequest> diagnostic_request_;
|
||||
|
26
src/config.h
26
src/config.h
@ -144,20 +144,18 @@ struct Config {
|
||||
// blacklisted files.
|
||||
std::vector<std::string> blacklist;
|
||||
|
||||
// How often should ccls publish diagnostics in completion?
|
||||
// -1: never
|
||||
// 0: as often as possible
|
||||
// xxx: at most every xxx milliseconds
|
||||
int frequencyMs = 0;
|
||||
// Time to wait before computing diagnostics for textDocument/didChange.
|
||||
// -1: disable diagnostics on change
|
||||
// 0: immediately
|
||||
// positive (e.g. 500): wait for 500 milliseconds. didChange requests in
|
||||
// this period of time will only cause one computation.
|
||||
int onChange = 1000;
|
||||
|
||||
// If true, diagnostics will be reported for textDocument/didChange.
|
||||
bool onChange = true;
|
||||
// Time to wait before computing diagnostics for textDocument/didOpen.
|
||||
int onOpen = 0;
|
||||
|
||||
// If true, diagnostics will be reported for textDocument/didOpen.
|
||||
bool onOpen = true;
|
||||
|
||||
// If true, diagnostics will be reported for textDocument/didSave.
|
||||
bool onSave = true;
|
||||
// Time to wait before computing diagnostics for textDocument/didSave.
|
||||
int onSave = 0;
|
||||
|
||||
bool spellChecking = true;
|
||||
|
||||
@ -246,8 +244,8 @@ MAKE_REFLECT_STRUCT(Config::Completion, caseSensitivity, detailedLabel,
|
||||
dropOldRequests, duplicateOptional, filterAndSort,
|
||||
includeBlacklist, includeMaxPathSize,
|
||||
includeSuffixWhitelist, includeWhitelist);
|
||||
MAKE_REFLECT_STRUCT(Config::Diagnostics, blacklist, frequencyMs, onChange,
|
||||
onOpen, onSave, spellChecking, whitelist)
|
||||
MAKE_REFLECT_STRUCT(Config::Diagnostics, blacklist, onChange, onOpen, onSave,
|
||||
spellChecking, whitelist)
|
||||
MAKE_REFLECT_STRUCT(Config::Highlight, lsRanges, blacklist, whitelist)
|
||||
MAKE_REFLECT_STRUCT(Config::Index, blacklist, comments, enabled, multiVersion,
|
||||
multiVersionBlacklist, multiVersionWhitelist, onChange,
|
||||
|
@ -15,7 +15,6 @@
|
||||
|
||||
struct CompletionManager;
|
||||
struct Config;
|
||||
class DiagnosticsPublisher;
|
||||
struct GroupMatch;
|
||||
struct VFS;
|
||||
struct IncludeComplete;
|
||||
@ -78,7 +77,6 @@ struct MessageHandler {
|
||||
DB *db = nullptr;
|
||||
MultiQueueWaiter *waiter = nullptr;
|
||||
Project *project = nullptr;
|
||||
DiagnosticsPublisher *diag_pub = nullptr;
|
||||
VFS *vfs = nullptr;
|
||||
SemanticHighlight *highlight = nullptr;
|
||||
WorkingFiles *working_files = nullptr;
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright 2017-2018 ccls Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#include "clang_complete.hh"
|
||||
#include "match.h"
|
||||
#include "message_handler.h"
|
||||
#include "pipeline.hh"
|
||||
@ -38,6 +39,7 @@ struct Handler_CclsReload : BaseMessageHandler<In_CclsReload> {
|
||||
vfs->Clear();
|
||||
db->clear();
|
||||
project->Index(working_files, lsRequestId());
|
||||
clang_complete->FlushAllSessions();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -474,7 +474,6 @@ struct Handler_Initialize : BaseMessageHandler<In_InitializeRequest> {
|
||||
sys::fs::create_directories(g_config->cacheDirectory + '@' + escaped);
|
||||
}
|
||||
|
||||
diag_pub->Init();
|
||||
idx::Init();
|
||||
highlight->Init();
|
||||
|
||||
|
@ -30,9 +30,8 @@ struct Handler_TextDocumentDidChange
|
||||
if (g_config->index.onChange)
|
||||
pipeline::Index(path, {}, IndexMode::OnChange);
|
||||
clang_complete->NotifyView(path);
|
||||
if (g_config->diagnostics.onChange)
|
||||
clang_complete->DiagnosticsUpdate(
|
||||
params.textDocument.AsTextDocumentIdentifier());
|
||||
if (g_config->diagnostics.onChange >= 0)
|
||||
clang_complete->DiagnosticsUpdate(path, g_config->diagnostics.onChange);
|
||||
}
|
||||
};
|
||||
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidChange);
|
||||
|
@ -35,7 +35,7 @@ struct Handler_TextDocumentDidClose
|
||||
|
||||
// Remove internal state.
|
||||
working_files->OnClose(request->params.textDocument);
|
||||
clang_complete->NotifyClose(path);
|
||||
clang_complete->OnClose(path);
|
||||
}
|
||||
};
|
||||
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidClose);
|
||||
|
@ -59,14 +59,10 @@ struct Handler_TextDocumentDidOpen
|
||||
project->SetArgsForFile(args, path);
|
||||
|
||||
// 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);
|
||||
clang_complete->FlushSession(path);
|
||||
}
|
||||
|
||||
clang_complete->NotifyView(path);
|
||||
if (g_config->diagnostics.onOpen)
|
||||
clang_complete->DiagnosticsUpdate({params.textDocument.uri});
|
||||
}
|
||||
};
|
||||
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidOpen);
|
||||
|
@ -52,12 +52,12 @@ struct Handler_WorkspaceDidChangeWatchedFiles
|
||||
if (mode == IndexMode::Normal)
|
||||
clang_complete->NotifySave(path);
|
||||
else
|
||||
clang_complete->FlushSession(path);
|
||||
clang_complete->OnClose(path);
|
||||
break;
|
||||
}
|
||||
case lsFileChangeType::Deleted:
|
||||
pipeline::Index(path, {}, mode);
|
||||
clang_complete->FlushSession(path);
|
||||
clang_complete->OnClose(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -27,41 +27,6 @@ using namespace llvm;
|
||||
#include <unistd.h>
|
||||
#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() {
|
||||
std::lock_guard lock(mutex);
|
||||
state.clear();
|
||||
@ -485,12 +450,15 @@ void MainLoop() {
|
||||
SemanticHighlight highlight;
|
||||
WorkingFiles working_files;
|
||||
VFS vfs;
|
||||
DiagnosticsPublisher diag_pub;
|
||||
|
||||
CompletionManager clang_complete(
|
||||
&project, &working_files,
|
||||
[&](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) {
|
||||
if (id.Valid()) {
|
||||
@ -511,7 +479,6 @@ void MainLoop() {
|
||||
handler->db = &db;
|
||||
handler->waiter = indexer_waiter;
|
||||
handler->project = &project;
|
||||
handler->diag_pub = &diag_pub;
|
||||
handler->vfs = &vfs;
|
||||
handler->highlight = &highlight;
|
||||
handler->working_files = &working_files;
|
||||
|
@ -19,18 +19,6 @@ struct Project;
|
||||
struct WorkingFiles;
|
||||
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 State {
|
||||
int64_t timestamp;
|
||||
|
@ -54,15 +54,6 @@ enum OptionClass {
|
||||
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 {
|
||||
ProjectConfig *config;
|
||||
std::unordered_set<size_t> command_set;
|
||||
|
@ -105,6 +105,15 @@ std::string EscapeFileName(std::string 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) {
|
||||
sys::fs::file_status Status;
|
||||
if (sys::fs::status(path, Status))
|
||||
|
@ -58,6 +58,9 @@ void EnsureEndsInSlash(std::string &path);
|
||||
// e.g. foo/bar.c => foo_bar.c
|
||||
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<std::string> ReadContent(const std::string &filename);
|
||||
void WriteToFile(const std::string &filename, const std::string &content);
|
||||
|
Loading…
Reference in New Issue
Block a user