2017-05-16 17:38:59 +08:00
|
|
|
//===--- ClangdServer.cpp - Main clangd server code --------------*- C++-*-===//
|
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===-------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
#include "ClangdServer.h"
|
2017-12-20 01:06:07 +08:00
|
|
|
#include "CodeComplete.h"
|
[clangd] Implementation of workspace/symbol request
Summary:
This is a basic implementation of the "workspace/symbol" request which is
used to find symbols by a string query. Since this is similar to code completion
in terms of result, this implementation reuses the "fuzzyFind" in order to get
matches. For now, the scoring algorithm is the same as code completion and
improvements could be done in the future.
The index model doesn't contain quite enough symbols for this to cover
common symbols like methods, enum class enumerators, functions in unamed
namespaces, etc. The index model will be augmented separately to achieve this.
Reviewers: sammccall, ilya-biryukov
Reviewed By: sammccall
Subscribers: jkorous, hokein, simark, sammccall, klimek, mgorny, ilya-biryukov, mgrang, jkorous-apple, ioeric, MaskRay, cfe-commits
Differential Revision: https://reviews.llvm.org/D44882
llvm-svn: 330637
2018-04-24 04:00:52 +08:00
|
|
|
#include "FindSymbols.h"
|
2018-02-16 22:15:55 +08:00
|
|
|
#include "Headers.h"
|
2017-12-19 20:23:48 +08:00
|
|
|
#include "SourceCode.h"
|
2017-12-20 01:06:07 +08:00
|
|
|
#include "XRefs.h"
|
2018-01-15 20:33:00 +08:00
|
|
|
#include "index/Merge.h"
|
2017-05-16 22:40:30 +08:00
|
|
|
#include "clang/Format/Format.h"
|
2017-05-16 17:38:59 +08:00
|
|
|
#include "clang/Frontend/CompilerInstance.h"
|
|
|
|
#include "clang/Frontend/CompilerInvocation.h"
|
2018-05-24 23:50:15 +08:00
|
|
|
#include "clang/Lex/Preprocessor.h"
|
2017-05-16 17:38:59 +08:00
|
|
|
#include "clang/Tooling/CompilationDatabase.h"
|
2017-11-16 02:04:56 +08:00
|
|
|
#include "clang/Tooling/Refactoring/RefactoringResultConsumer.h"
|
|
|
|
#include "clang/Tooling/Refactoring/Rename/RenamingAction.h"
|
2017-05-16 22:40:30 +08:00
|
|
|
#include "llvm/ADT/ArrayRef.h"
|
2018-01-26 01:01:39 +08:00
|
|
|
#include "llvm/ADT/ScopeExit.h"
|
2017-10-26 20:28:13 +08:00
|
|
|
#include "llvm/Support/Errc.h"
|
2017-05-16 17:38:59 +08:00
|
|
|
#include "llvm/Support/FileSystem.h"
|
2017-09-27 23:31:17 +08:00
|
|
|
#include "llvm/Support/Path.h"
|
2017-05-23 21:42:59 +08:00
|
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
#include <future>
|
2017-05-16 17:38:59 +08:00
|
|
|
|
2017-05-16 18:06:20 +08:00
|
|
|
using namespace clang;
|
2017-05-16 17:38:59 +08:00
|
|
|
using namespace clang::clangd;
|
|
|
|
|
2017-05-16 22:40:30 +08:00
|
|
|
namespace {
|
|
|
|
|
2018-01-31 16:51:16 +08:00
|
|
|
void ignoreError(llvm::Error Err) {
|
|
|
|
handleAllErrors(std::move(Err), [](const llvm::ErrorInfoBase &) {});
|
|
|
|
}
|
|
|
|
|
2017-06-28 18:34:50 +08:00
|
|
|
std::string getStandardResourceDir() {
|
|
|
|
static int Dummy; // Just an address in this process.
|
|
|
|
return CompilerInvocation::GetResourcesPath("clangd", (void *)&Dummy);
|
|
|
|
}
|
|
|
|
|
2017-11-09 19:30:04 +08:00
|
|
|
class RefactoringResultCollector final
|
|
|
|
: public tooling::RefactoringResultConsumer {
|
|
|
|
public:
|
|
|
|
void handleError(llvm::Error Err) override {
|
|
|
|
assert(!Result.hasValue());
|
|
|
|
// FIXME: figure out a way to return better message for DiagnosticError.
|
|
|
|
// clangd uses llvm::toString to convert the Err to string, however, for
|
|
|
|
// DiagnosticError, only "clang diagnostic" will be generated.
|
|
|
|
Result = std::move(Err);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Using the handle(SymbolOccurrences) from parent class.
|
|
|
|
using tooling::RefactoringResultConsumer::handle;
|
|
|
|
|
|
|
|
void handle(tooling::AtomicChanges SourceReplacements) override {
|
|
|
|
assert(!Result.hasValue());
|
|
|
|
Result = std::move(SourceReplacements);
|
|
|
|
}
|
|
|
|
|
|
|
|
Optional<Expected<tooling::AtomicChanges>> Result;
|
|
|
|
};
|
|
|
|
|
2017-05-16 22:40:30 +08:00
|
|
|
} // namespace
|
|
|
|
|
2018-03-13 07:22:35 +08:00
|
|
|
IntrusiveRefCntPtr<vfs::FileSystem> RealFileSystemProvider::getFileSystem() {
|
|
|
|
return vfs::getRealFileSystem();
|
2017-05-26 20:26:51 +08:00
|
|
|
}
|
|
|
|
|
2018-03-06 01:28:54 +08:00
|
|
|
ClangdServer::Options ClangdServer::optsForTest() {
|
|
|
|
ClangdServer::Options Opts;
|
|
|
|
Opts.UpdateDebounce = std::chrono::steady_clock::duration::zero(); // Faster!
|
|
|
|
Opts.StorePreamblesInMemory = true;
|
|
|
|
Opts.AsyncThreadsCount = 4; // Consistent!
|
|
|
|
return Opts;
|
|
|
|
}
|
|
|
|
|
2017-11-24 00:58:22 +08:00
|
|
|
ClangdServer::ClangdServer(GlobalCompilationDatabase &CDB,
|
|
|
|
FileSystemProvider &FSProvider,
|
2018-03-06 01:28:54 +08:00
|
|
|
DiagnosticsConsumer &DiagConsumer,
|
|
|
|
const Options &Opts)
|
|
|
|
: CompileArgs(CDB, Opts.ResourceDir ? Opts.ResourceDir->str()
|
|
|
|
: getStandardResourceDir()),
|
2018-01-25 22:19:21 +08:00
|
|
|
DiagConsumer(DiagConsumer), FSProvider(FSProvider),
|
2018-03-06 01:28:54 +08:00
|
|
|
FileIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex() : nullptr),
|
2018-01-31 16:51:16 +08:00
|
|
|
PCHs(std::make_shared<PCHContainerOperations>()),
|
|
|
|
// Pass a callback into `WorkScheduler` to extract symbols from a newly
|
|
|
|
// parsed file and rebuild the file index synchronously each time an AST
|
|
|
|
// is parsed.
|
2017-12-20 02:00:37 +08:00
|
|
|
// FIXME(ioeric): this can be slow and we may be able to index on less
|
|
|
|
// critical paths.
|
2018-05-24 23:50:15 +08:00
|
|
|
WorkScheduler(
|
|
|
|
Opts.AsyncThreadsCount, Opts.StorePreamblesInMemory,
|
|
|
|
FileIdx
|
|
|
|
? [this](PathRef Path, ASTContext &AST,
|
|
|
|
std::shared_ptr<Preprocessor>
|
|
|
|
PP) { FileIdx->update(Path, &AST, std::move(PP)); }
|
|
|
|
: PreambleParsedCallback(),
|
[clangd] Keep only a limited number of idle ASTs in memory
Summary:
After this commit, clangd will only keep the last 3 accessed ASTs in
memory. Preambles for each of the opened files are still kept in
memory to make completion and AST rebuilds fast.
AST rebuilds are usually fast enough, but having the last ASTs in
memory still considerably improves latency of operations like
findDefinition and documeneHighlight, which are often sent multiple
times a second when moving around the code. So keeping some of the last
accessed ASTs in memory seems like a reasonable tradeoff.
Reviewers: sammccall
Reviewed By: sammccall
Subscribers: malaperle, arphaman, klimek, javed.absar, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D47063
llvm-svn: 333737
2018-06-01 18:08:43 +08:00
|
|
|
Opts.UpdateDebounce, Opts.RetentionPolicy) {
|
2018-03-06 01:28:54 +08:00
|
|
|
if (FileIdx && Opts.StaticIndex) {
|
|
|
|
MergedIndex = mergeIndex(FileIdx.get(), Opts.StaticIndex);
|
2018-01-15 20:33:00 +08:00
|
|
|
Index = MergedIndex.get();
|
|
|
|
} else if (FileIdx)
|
|
|
|
Index = FileIdx.get();
|
2018-03-06 01:28:54 +08:00
|
|
|
else if (Opts.StaticIndex)
|
|
|
|
Index = Opts.StaticIndex;
|
2018-01-15 20:33:00 +08:00
|
|
|
else
|
|
|
|
Index = nullptr;
|
|
|
|
}
|
2017-05-16 17:38:59 +08:00
|
|
|
|
2017-09-27 23:31:17 +08:00
|
|
|
void ClangdServer::setRootPath(PathRef RootPath) {
|
|
|
|
std::string NewRootPath = llvm::sys::path::convert_to_slash(
|
|
|
|
RootPath, llvm::sys::path::Style::posix);
|
|
|
|
if (llvm::sys::fs::is_directory(NewRootPath))
|
|
|
|
this->RootPath = NewRootPath;
|
|
|
|
}
|
|
|
|
|
2018-02-22 21:11:12 +08:00
|
|
|
void ClangdServer::addDocument(PathRef File, StringRef Contents,
|
2018-03-15 01:08:41 +08:00
|
|
|
WantDiagnostics WantDiags, bool SkipCache) {
|
|
|
|
if (SkipCache)
|
|
|
|
CompileArgs.invalidate(File);
|
|
|
|
|
2018-03-16 22:30:42 +08:00
|
|
|
DocVersion Version = ++InternalVersion[File];
|
2018-03-15 01:08:41 +08:00
|
|
|
ParseInputs Inputs = {CompileArgs.getCompileCommand(File),
|
|
|
|
FSProvider.getFileSystem(), Contents.str()};
|
|
|
|
|
|
|
|
Path FileStr = File.str();
|
|
|
|
WorkScheduler.update(File, std::move(Inputs), WantDiags,
|
|
|
|
[this, FileStr, Version](std::vector<Diag> Diags) {
|
|
|
|
consumeDiagnostics(FileStr, Version, std::move(Diags));
|
|
|
|
});
|
2017-05-16 17:38:59 +08:00
|
|
|
}
|
|
|
|
|
2018-02-08 15:37:35 +08:00
|
|
|
void ClangdServer::removeDocument(PathRef File) {
|
2018-03-16 22:30:42 +08:00
|
|
|
++InternalVersion[File];
|
2018-01-25 22:19:21 +08:00
|
|
|
CompileArgs.invalidate(File);
|
2018-02-08 15:37:35 +08:00
|
|
|
WorkScheduler.remove(File);
|
2017-05-16 17:38:59 +08:00
|
|
|
}
|
|
|
|
|
2018-03-13 07:22:35 +08:00
|
|
|
void ClangdServer::codeComplete(PathRef File, Position Pos,
|
|
|
|
const clangd::CodeCompleteOptions &Opts,
|
|
|
|
Callback<CompletionList> CB) {
|
2017-12-05 18:42:57 +08:00
|
|
|
// Copy completion options for passing them to async task handler.
|
|
|
|
auto CodeCompleteOpts = Opts;
|
2018-01-15 20:33:00 +08:00
|
|
|
if (!CodeCompleteOpts.Index) // Respect overridden index.
|
|
|
|
CodeCompleteOpts.Index = Index;
|
2018-01-09 22:39:27 +08:00
|
|
|
|
|
|
|
// Copy PCHs to avoid accessing this->PCHs concurrently
|
|
|
|
std::shared_ptr<PCHContainerOperations> PCHs = this->PCHs;
|
2018-03-13 07:22:35 +08:00
|
|
|
auto FS = FSProvider.getFileSystem();
|
2018-03-15 02:31:48 +08:00
|
|
|
auto Task = [PCHs, Pos, FS,
|
|
|
|
CodeCompleteOpts](Path File, Callback<CompletionList> CB,
|
|
|
|
llvm::Expected<InputsAndPreamble> IP) {
|
2018-03-16 22:30:42 +08:00
|
|
|
if (!IP)
|
|
|
|
return CB(IP.takeError());
|
|
|
|
|
2018-01-31 16:51:16 +08:00
|
|
|
auto PreambleData = IP->Preamble;
|
|
|
|
|
|
|
|
// FIXME(ibiryukov): even if Preamble is non-null, we may want to check
|
|
|
|
// both the old and the new version in case only one of them matches.
|
|
|
|
CompletionList Result = clangd::codeComplete(
|
2018-03-15 01:46:52 +08:00
|
|
|
File, IP->Command, PreambleData ? &PreambleData->Preamble : nullptr,
|
2018-05-15 23:29:32 +08:00
|
|
|
PreambleData ? PreambleData->Inclusions : std::vector<Inclusion>(),
|
2018-03-15 02:31:48 +08:00
|
|
|
IP->Contents, Pos, FS, PCHs, CodeCompleteOpts);
|
2018-03-13 07:22:35 +08:00
|
|
|
CB(std::move(Result));
|
2018-01-31 16:51:16 +08:00
|
|
|
};
|
|
|
|
|
2018-03-15 02:31:48 +08:00
|
|
|
WorkScheduler.runWithPreamble("CodeComplete", File,
|
|
|
|
Bind(Task, File.str(), std::move(CB)));
|
2017-05-16 17:38:59 +08:00
|
|
|
}
|
2017-05-23 21:42:59 +08:00
|
|
|
|
2018-03-13 07:22:35 +08:00
|
|
|
void ClangdServer::signatureHelp(PathRef File, Position Pos,
|
|
|
|
Callback<SignatureHelp> CB) {
|
2017-10-06 19:54:17 +08:00
|
|
|
|
2018-02-15 21:15:47 +08:00
|
|
|
auto PCHs = this->PCHs;
|
2018-03-13 07:22:35 +08:00
|
|
|
auto FS = FSProvider.getFileSystem();
|
2018-03-15 02:31:48 +08:00
|
|
|
auto Action = [Pos, FS, PCHs](Path File, Callback<SignatureHelp> CB,
|
2018-03-13 07:22:35 +08:00
|
|
|
llvm::Expected<InputsAndPreamble> IP) {
|
2018-01-31 16:51:16 +08:00
|
|
|
if (!IP)
|
2018-03-13 07:22:35 +08:00
|
|
|
return CB(IP.takeError());
|
2018-02-15 21:15:47 +08:00
|
|
|
|
2018-01-31 16:51:16 +08:00
|
|
|
auto PreambleData = IP->Preamble;
|
2018-03-15 01:46:52 +08:00
|
|
|
CB(clangd::signatureHelp(File, IP->Command,
|
2018-03-13 07:22:35 +08:00
|
|
|
PreambleData ? &PreambleData->Preamble : nullptr,
|
2018-03-15 02:31:48 +08:00
|
|
|
IP->Contents, Pos, FS, PCHs));
|
2018-01-31 16:51:16 +08:00
|
|
|
};
|
2018-02-15 21:15:47 +08:00
|
|
|
|
2018-03-15 02:31:48 +08:00
|
|
|
WorkScheduler.runWithPreamble("SignatureHelp", File,
|
|
|
|
Bind(Action, File.str(), std::move(CB)));
|
2017-10-06 19:54:17 +08:00
|
|
|
}
|
|
|
|
|
2017-12-13 04:25:06 +08:00
|
|
|
llvm::Expected<tooling::Replacements>
|
|
|
|
ClangdServer::formatRange(StringRef Code, PathRef File, Range Rng) {
|
Make positionToOffset return llvm::Expected<size_t>
Summary:
To implement incremental document syncing, we want to verify that the
ranges provided by the front-end are valid. Currently, positionToOffset
deals with invalid Positions by returning 0 or Code.size(), which are
two valid offsets. Instead, return an llvm:Expected<size_t> with an
error if the position is invalid.
According to the LSP, if the character value exceeds the number of
characters of the given line, it should default back to the end of the
line. It makes sense in some contexts to have this behavior, and does
not in other contexts. The AllowColumnsBeyondLineLength parameter
allows to decide what to do in that case, default back to the end of the
line, or return an error.
Reviewers: ilya-biryukov
Subscribers: klimek, ilya-biryukov, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D44673
llvm-svn: 328100
2018-03-21 22:36:46 +08:00
|
|
|
llvm::Expected<size_t> Begin = positionToOffset(Code, Rng.start);
|
|
|
|
if (!Begin)
|
|
|
|
return Begin.takeError();
|
|
|
|
llvm::Expected<size_t> End = positionToOffset(Code, Rng.end);
|
|
|
|
if (!End)
|
|
|
|
return End.takeError();
|
|
|
|
return formatCode(Code, File, {tooling::Range(*Begin, *End - *Begin)});
|
2017-05-16 22:40:30 +08:00
|
|
|
}
|
|
|
|
|
2017-12-13 04:25:06 +08:00
|
|
|
llvm::Expected<tooling::Replacements> ClangdServer::formatFile(StringRef Code,
|
|
|
|
PathRef File) {
|
2017-05-16 22:40:30 +08:00
|
|
|
// Format everything.
|
|
|
|
return formatCode(Code, File, {tooling::Range(0, Code.size())});
|
|
|
|
}
|
|
|
|
|
2017-12-13 04:25:06 +08:00
|
|
|
llvm::Expected<tooling::Replacements>
|
|
|
|
ClangdServer::formatOnType(StringRef Code, PathRef File, Position Pos) {
|
2017-05-16 22:40:30 +08:00
|
|
|
// Look for the previous opening brace from the character position and
|
|
|
|
// format starting from there.
|
Make positionToOffset return llvm::Expected<size_t>
Summary:
To implement incremental document syncing, we want to verify that the
ranges provided by the front-end are valid. Currently, positionToOffset
deals with invalid Positions by returning 0 or Code.size(), which are
two valid offsets. Instead, return an llvm:Expected<size_t> with an
error if the position is invalid.
According to the LSP, if the character value exceeds the number of
characters of the given line, it should default back to the end of the
line. It makes sense in some contexts to have this behavior, and does
not in other contexts. The AllowColumnsBeyondLineLength parameter
allows to decide what to do in that case, default back to the end of the
line, or return an error.
Reviewers: ilya-biryukov
Subscribers: klimek, ilya-biryukov, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D44673
llvm-svn: 328100
2018-03-21 22:36:46 +08:00
|
|
|
llvm::Expected<size_t> CursorPos = positionToOffset(Code, Pos);
|
|
|
|
if (!CursorPos)
|
|
|
|
return CursorPos.takeError();
|
|
|
|
size_t PreviousLBracePos = StringRef(Code).find_last_of('{', *CursorPos);
|
2017-05-16 22:40:30 +08:00
|
|
|
if (PreviousLBracePos == StringRef::npos)
|
Make positionToOffset return llvm::Expected<size_t>
Summary:
To implement incremental document syncing, we want to verify that the
ranges provided by the front-end are valid. Currently, positionToOffset
deals with invalid Positions by returning 0 or Code.size(), which are
two valid offsets. Instead, return an llvm:Expected<size_t> with an
error if the position is invalid.
According to the LSP, if the character value exceeds the number of
characters of the given line, it should default back to the end of the
line. It makes sense in some contexts to have this behavior, and does
not in other contexts. The AllowColumnsBeyondLineLength parameter
allows to decide what to do in that case, default back to the end of the
line, or return an error.
Reviewers: ilya-biryukov
Subscribers: klimek, ilya-biryukov, jkorous-apple, ioeric, cfe-commits
Differential Revision: https://reviews.llvm.org/D44673
llvm-svn: 328100
2018-03-21 22:36:46 +08:00
|
|
|
PreviousLBracePos = *CursorPos;
|
|
|
|
size_t Len = *CursorPos - PreviousLBracePos;
|
2017-05-16 22:40:30 +08:00
|
|
|
|
|
|
|
return formatCode(Code, File, {tooling::Range(PreviousLBracePos, Len)});
|
|
|
|
}
|
2017-05-16 17:38:59 +08:00
|
|
|
|
2018-03-13 07:22:35 +08:00
|
|
|
void ClangdServer::rename(PathRef File, Position Pos, llvm::StringRef NewName,
|
|
|
|
Callback<std::vector<tooling::Replacement>> CB) {
|
2018-02-15 21:15:47 +08:00
|
|
|
auto Action = [Pos](Path File, std::string NewName,
|
2018-03-13 07:22:35 +08:00
|
|
|
Callback<std::vector<tooling::Replacement>> CB,
|
2018-02-15 21:15:47 +08:00
|
|
|
Expected<InputsAndAST> InpAST) {
|
2018-01-31 16:51:16 +08:00
|
|
|
if (!InpAST)
|
2018-03-13 07:22:35 +08:00
|
|
|
return CB(InpAST.takeError());
|
2018-01-31 16:51:16 +08:00
|
|
|
auto &AST = InpAST->AST;
|
|
|
|
|
|
|
|
RefactoringResultCollector ResultCollector;
|
|
|
|
const SourceManager &SourceMgr = AST.getASTContext().getSourceManager();
|
2017-11-09 19:30:04 +08:00
|
|
|
SourceLocation SourceLocationBeg =
|
[clangd] Fix unicode handling, using UTF-16 where LSP requires it.
Summary:
The Language Server Protocol unfortunately mandates that locations in files
be represented by line/column pairs, where the "column" is actually an index
into the UTF-16-encoded text of the line.
(This is because VSCode is written in JavaScript, which is UTF-16-native).
Internally clangd treats source files at UTF-8, the One True Encoding, and
generally deals with byte offsets (though there are exceptions).
Before this patch, conversions between offsets and LSP Position pretended
that Position.character was UTF-8 bytes, which is only true for ASCII lines.
Now we examine the text to convert correctly (but don't actually need to
transcode it, due to some nice details of the encodings).
The updated functions in SourceCode are the blessed way to interact with
the Position.character field, and anything else is likely to be wrong.
So I also updated the other accesses:
- CodeComplete needs a "clang-style" line/column, with column in utf-8 bytes.
This is now converted via Position -> offset -> clang line/column
(a new function is added to SourceCode.h for the second conversion).
- getBeginningOfIdentifier skipped backwards in UTF-16 space, which is will
behave badly when it splits a surrogate pair. Skipping backwards in UTF-8
coordinates gives the lexer a fighting chance of getting this right.
While here, I clarified(?) the logic comments, fixed a bug with identifiers
containing digits, simplified the signature slightly and added a test.
This seems likely to cause problems with editors that have the same bug, and
treat the protocol as if columns are UTF-8 bytes. But we can find and fix those.
Reviewers: hokein
Subscribers: klimek, ilya-biryukov, ioeric, MaskRay, jkorous, cfe-commits
Differential Revision: https://reviews.llvm.org/D46035
llvm-svn: 331029
2018-04-27 19:59:28 +08:00
|
|
|
clangd::getBeginningOfIdentifier(AST, Pos, SourceMgr.getMainFileID());
|
2017-11-09 19:30:04 +08:00
|
|
|
tooling::RefactoringRuleContext Context(
|
2018-01-31 16:51:16 +08:00
|
|
|
AST.getASTContext().getSourceManager());
|
|
|
|
Context.setASTContext(AST.getASTContext());
|
2017-11-09 19:30:04 +08:00
|
|
|
auto Rename = clang::tooling::RenameOccurrences::initiate(
|
2018-02-15 21:15:47 +08:00
|
|
|
Context, SourceRange(SourceLocationBeg), NewName);
|
2018-01-31 16:51:16 +08:00
|
|
|
if (!Rename)
|
2018-03-13 07:22:35 +08:00
|
|
|
return CB(Rename.takeError());
|
2018-01-31 16:51:16 +08:00
|
|
|
|
2017-11-09 19:30:04 +08:00
|
|
|
Rename->invoke(ResultCollector, Context);
|
2018-01-31 16:51:16 +08:00
|
|
|
|
|
|
|
assert(ResultCollector.Result.hasValue());
|
|
|
|
if (!ResultCollector.Result.getValue())
|
2018-03-13 07:22:35 +08:00
|
|
|
return CB(ResultCollector.Result->takeError());
|
2018-01-31 16:51:16 +08:00
|
|
|
|
|
|
|
std::vector<tooling::Replacement> Replacements;
|
|
|
|
for (const tooling::AtomicChange &Change : ResultCollector.Result->get()) {
|
|
|
|
tooling::Replacements ChangeReps = Change.getReplacements();
|
|
|
|
for (const auto &Rep : ChangeReps) {
|
|
|
|
// FIXME: Right now we only support renaming the main file, so we
|
|
|
|
// drop replacements not for the main file. In the future, we might
|
|
|
|
// consider to support:
|
|
|
|
// * rename in any included header
|
|
|
|
// * rename only in the "main" header
|
|
|
|
// * provide an error if there are symbols we won't rename (e.g.
|
|
|
|
// std::vector)
|
|
|
|
// * rename globally in project
|
|
|
|
// * rename in open files
|
|
|
|
if (Rep.getFilePath() == File)
|
|
|
|
Replacements.push_back(Rep);
|
|
|
|
}
|
2017-11-09 19:30:04 +08:00
|
|
|
}
|
2018-03-13 07:22:35 +08:00
|
|
|
return CB(std::move(Replacements));
|
2018-01-31 16:51:16 +08:00
|
|
|
};
|
2018-02-15 21:15:47 +08:00
|
|
|
|
|
|
|
WorkScheduler.runWithAST(
|
2018-03-13 07:22:35 +08:00
|
|
|
"Rename", File, Bind(Action, File.str(), NewName.str(), std::move(CB)));
|
2017-11-09 19:30:04 +08:00
|
|
|
}
|
|
|
|
|
2018-02-15 21:15:47 +08:00
|
|
|
void ClangdServer::dumpAST(PathRef File,
|
|
|
|
UniqueFunction<void(std::string)> Callback) {
|
|
|
|
auto Action = [](decltype(Callback) Callback,
|
|
|
|
llvm::Expected<InputsAndAST> InpAST) {
|
2018-01-31 16:51:16 +08:00
|
|
|
if (!InpAST) {
|
|
|
|
ignoreError(InpAST.takeError());
|
2018-02-15 21:15:47 +08:00
|
|
|
return Callback("<no-ast>");
|
2018-01-31 16:51:16 +08:00
|
|
|
}
|
|
|
|
std::string Result;
|
2017-08-01 23:51:38 +08:00
|
|
|
|
|
|
|
llvm::raw_string_ostream ResultOS(Result);
|
2018-01-31 16:51:16 +08:00
|
|
|
clangd::dumpAST(InpAST->AST, ResultOS);
|
2017-08-01 23:51:38 +08:00
|
|
|
ResultOS.flush();
|
2018-01-31 16:51:16 +08:00
|
|
|
|
2018-02-15 21:15:47 +08:00
|
|
|
Callback(Result);
|
2018-01-31 16:51:16 +08:00
|
|
|
};
|
2018-02-15 21:15:47 +08:00
|
|
|
|
2018-02-23 15:54:17 +08:00
|
|
|
WorkScheduler.runWithAST("DumpAST", File, Bind(Action, std::move(Callback)));
|
2017-05-16 17:38:59 +08:00
|
|
|
}
|
2017-06-29 00:12:10 +08:00
|
|
|
|
2018-03-13 07:22:35 +08:00
|
|
|
void ClangdServer::findDefinitions(PathRef File, Position Pos,
|
|
|
|
Callback<std::vector<Location>> CB) {
|
2018-06-05 22:07:45 +08:00
|
|
|
auto Action = [Pos, this](Callback<std::vector<Location>> CB,
|
|
|
|
llvm::Expected<InputsAndAST> InpAST) {
|
2018-01-31 16:51:16 +08:00
|
|
|
if (!InpAST)
|
2018-03-13 07:22:35 +08:00
|
|
|
return CB(InpAST.takeError());
|
2018-04-30 23:24:17 +08:00
|
|
|
CB(clangd::findDefinitions(InpAST->AST, Pos, this->FileIdx.get()));
|
2018-01-31 16:51:16 +08:00
|
|
|
};
|
2018-02-15 21:15:47 +08:00
|
|
|
|
2018-03-13 07:22:35 +08:00
|
|
|
WorkScheduler.runWithAST("Definitions", File, Bind(Action, std::move(CB)));
|
2017-06-29 00:12:10 +08:00
|
|
|
}
|
2017-08-14 16:17:24 +08:00
|
|
|
|
2017-09-28 11:14:40 +08:00
|
|
|
llvm::Optional<Path> ClangdServer::switchSourceHeader(PathRef Path) {
|
|
|
|
|
|
|
|
StringRef SourceExtensions[] = {".cpp", ".c", ".cc", ".cxx",
|
|
|
|
".c++", ".m", ".mm"};
|
|
|
|
StringRef HeaderExtensions[] = {".h", ".hh", ".hpp", ".hxx", ".inc"};
|
|
|
|
|
|
|
|
StringRef PathExt = llvm::sys::path::extension(Path);
|
|
|
|
|
|
|
|
// Lookup in a list of known extensions.
|
|
|
|
auto SourceIter =
|
|
|
|
std::find_if(std::begin(SourceExtensions), std::end(SourceExtensions),
|
|
|
|
[&PathExt](PathRef SourceExt) {
|
|
|
|
return SourceExt.equals_lower(PathExt);
|
|
|
|
});
|
|
|
|
bool IsSource = SourceIter != std::end(SourceExtensions);
|
|
|
|
|
|
|
|
auto HeaderIter =
|
|
|
|
std::find_if(std::begin(HeaderExtensions), std::end(HeaderExtensions),
|
|
|
|
[&PathExt](PathRef HeaderExt) {
|
|
|
|
return HeaderExt.equals_lower(PathExt);
|
|
|
|
});
|
|
|
|
|
|
|
|
bool IsHeader = HeaderIter != std::end(HeaderExtensions);
|
|
|
|
|
2018-05-22 21:29:37 +08:00
|
|
|
// We can only switch between the known extensions.
|
2017-09-28 11:14:40 +08:00
|
|
|
if (!IsSource && !IsHeader)
|
|
|
|
return llvm::None;
|
|
|
|
|
|
|
|
// Array to lookup extensions for the switch. An opposite of where original
|
|
|
|
// extension was found.
|
|
|
|
ArrayRef<StringRef> NewExts;
|
|
|
|
if (IsSource)
|
|
|
|
NewExts = HeaderExtensions;
|
|
|
|
else
|
|
|
|
NewExts = SourceExtensions;
|
|
|
|
|
|
|
|
// Storage for the new path.
|
|
|
|
SmallString<128> NewPath = StringRef(Path);
|
|
|
|
|
|
|
|
// Instance of vfs::FileSystem, used for file existence checks.
|
2018-03-13 07:22:35 +08:00
|
|
|
auto FS = FSProvider.getFileSystem();
|
2017-09-28 11:14:40 +08:00
|
|
|
|
|
|
|
// Loop through switched extension candidates.
|
|
|
|
for (StringRef NewExt : NewExts) {
|
|
|
|
llvm::sys::path::replace_extension(NewPath, NewExt);
|
|
|
|
if (FS->exists(NewPath))
|
|
|
|
return NewPath.str().str(); // First str() to convert from SmallString to
|
|
|
|
// StringRef, second to convert from StringRef
|
|
|
|
// to std::string
|
2017-10-06 22:39:39 +08:00
|
|
|
|
2017-09-28 11:14:40 +08:00
|
|
|
// Also check NewExt in upper-case, just in case.
|
|
|
|
llvm::sys::path::replace_extension(NewPath, NewExt.upper());
|
|
|
|
if (FS->exists(NewPath))
|
|
|
|
return NewPath.str().str();
|
|
|
|
}
|
|
|
|
|
|
|
|
return llvm::None;
|
|
|
|
}
|
|
|
|
|
2017-12-13 04:25:06 +08:00
|
|
|
llvm::Expected<tooling::Replacements>
|
|
|
|
ClangdServer::formatCode(llvm::StringRef Code, PathRef File,
|
|
|
|
ArrayRef<tooling::Range> Ranges) {
|
|
|
|
// Call clang-format.
|
2018-03-13 07:22:35 +08:00
|
|
|
auto FS = FSProvider.getFileSystem();
|
|
|
|
auto Style = format::getStyle("file", File, "LLVM", Code, FS.get());
|
2018-03-06 18:42:50 +08:00
|
|
|
if (!Style)
|
|
|
|
return Style.takeError();
|
|
|
|
|
|
|
|
tooling::Replacements IncludeReplaces =
|
|
|
|
format::sortIncludes(*Style, Code, Ranges, File);
|
|
|
|
auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces);
|
|
|
|
if (!Changed)
|
|
|
|
return Changed.takeError();
|
|
|
|
|
|
|
|
return IncludeReplaces.merge(format::reformat(
|
|
|
|
Style.get(), *Changed,
|
|
|
|
tooling::calculateRangesAfterReplacements(IncludeReplaces, Ranges),
|
|
|
|
File));
|
2017-12-13 04:25:06 +08:00
|
|
|
}
|
|
|
|
|
2018-02-15 21:15:47 +08:00
|
|
|
void ClangdServer::findDocumentHighlights(
|
2018-03-13 07:22:35 +08:00
|
|
|
PathRef File, Position Pos, Callback<std::vector<DocumentHighlight>> CB) {
|
2018-06-05 22:07:45 +08:00
|
|
|
auto Action = [Pos](Callback<std::vector<DocumentHighlight>> CB,
|
|
|
|
llvm::Expected<InputsAndAST> InpAST) {
|
2018-01-31 16:51:16 +08:00
|
|
|
if (!InpAST)
|
2018-03-13 07:22:35 +08:00
|
|
|
return CB(InpAST.takeError());
|
|
|
|
CB(clangd::findDocumentHighlights(InpAST->AST, Pos));
|
2018-01-31 16:51:16 +08:00
|
|
|
};
|
2018-02-15 21:15:47 +08:00
|
|
|
|
2018-03-13 07:22:35 +08:00
|
|
|
WorkScheduler.runWithAST("Highlights", File, Bind(Action, std::move(CB)));
|
[clangd] Implement textDocument/hover
Summary: Implemention of textDocument/hover as described in LSP definition.
This patch adds a basic Hover implementation. When hovering a variable,
function, method or namespace, clangd will return a text containing the
declaration's scope, as well as the declaration of the hovered entity.
For example, for a variable:
Declared in class Foo::Bar
int hello = 2
For macros, the macro definition is returned.
This patch doesn't include:
- markdown support (the client I use doesn't support it yet)
- range support (optional in the Hover response)
- comments associated to variables/functions/classes
They are kept as future work to keep this patch simpler.
I added tests in XRefsTests.cpp. hover.test contains one simple
smoketest to make sure the feature works from a black box perspective.
Reviewers: malaperle, krasimir, bkramer, ilya-biryukov
Subscribers: sammccall, mgrang, klimek, rwols, ilya-biryukov, arphaman, cfe-commits
Differential Revision: https://reviews.llvm.org/D35894
Signed-off-by: Simon Marchi <simon.marchi@ericsson.com>
Signed-off-by: William Enright <william.enright@polymtl.ca>
llvm-svn: 325395
2018-02-17 05:38:15 +08:00
|
|
|
}
|
|
|
|
|
2018-06-04 18:37:16 +08:00
|
|
|
void ClangdServer::findHover(PathRef File, Position Pos,
|
|
|
|
Callback<llvm::Optional<Hover>> CB) {
|
2018-06-05 22:07:45 +08:00
|
|
|
auto Action = [Pos](Callback<llvm::Optional<Hover>> CB,
|
|
|
|
llvm::Expected<InputsAndAST> InpAST) {
|
[clangd] Implement textDocument/hover
Summary: Implemention of textDocument/hover as described in LSP definition.
This patch adds a basic Hover implementation. When hovering a variable,
function, method or namespace, clangd will return a text containing the
declaration's scope, as well as the declaration of the hovered entity.
For example, for a variable:
Declared in class Foo::Bar
int hello = 2
For macros, the macro definition is returned.
This patch doesn't include:
- markdown support (the client I use doesn't support it yet)
- range support (optional in the Hover response)
- comments associated to variables/functions/classes
They are kept as future work to keep this patch simpler.
I added tests in XRefsTests.cpp. hover.test contains one simple
smoketest to make sure the feature works from a black box perspective.
Reviewers: malaperle, krasimir, bkramer, ilya-biryukov
Subscribers: sammccall, mgrang, klimek, rwols, ilya-biryukov, arphaman, cfe-commits
Differential Revision: https://reviews.llvm.org/D35894
Signed-off-by: Simon Marchi <simon.marchi@ericsson.com>
Signed-off-by: William Enright <william.enright@polymtl.ca>
llvm-svn: 325395
2018-02-17 05:38:15 +08:00
|
|
|
if (!InpAST)
|
2018-03-13 07:22:35 +08:00
|
|
|
return CB(InpAST.takeError());
|
|
|
|
CB(clangd::getHover(InpAST->AST, Pos));
|
[clangd] Implement textDocument/hover
Summary: Implemention of textDocument/hover as described in LSP definition.
This patch adds a basic Hover implementation. When hovering a variable,
function, method or namespace, clangd will return a text containing the
declaration's scope, as well as the declaration of the hovered entity.
For example, for a variable:
Declared in class Foo::Bar
int hello = 2
For macros, the macro definition is returned.
This patch doesn't include:
- markdown support (the client I use doesn't support it yet)
- range support (optional in the Hover response)
- comments associated to variables/functions/classes
They are kept as future work to keep this patch simpler.
I added tests in XRefsTests.cpp. hover.test contains one simple
smoketest to make sure the feature works from a black box perspective.
Reviewers: malaperle, krasimir, bkramer, ilya-biryukov
Subscribers: sammccall, mgrang, klimek, rwols, ilya-biryukov, arphaman, cfe-commits
Differential Revision: https://reviews.llvm.org/D35894
Signed-off-by: Simon Marchi <simon.marchi@ericsson.com>
Signed-off-by: William Enright <william.enright@polymtl.ca>
llvm-svn: 325395
2018-02-17 05:38:15 +08:00
|
|
|
};
|
|
|
|
|
2018-03-13 07:22:35 +08:00
|
|
|
WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB)));
|
[clangd] Document highlights for clangd
Summary: Implementation of Document Highlights Request as described in
LSP.
Contributed by William Enright (nebiroth).
Reviewers: malaperle, krasimir, bkramer, ilya-biryukov
Reviewed By: malaperle
Subscribers: mgrang, sammccall, klimek, ioeric, rwols, cfe-commits, arphaman, ilya-biryukov
Differential Revision: https://reviews.llvm.org/D38425
llvm-svn: 320474
2017-12-12 20:27:47 +08:00
|
|
|
}
|
|
|
|
|
2018-03-15 01:08:41 +08:00
|
|
|
void ClangdServer::consumeDiagnostics(PathRef File, DocVersion Version,
|
|
|
|
std::vector<Diag> Diags) {
|
|
|
|
// We need to serialize access to resulting diagnostics to avoid calling
|
|
|
|
// `onDiagnosticsReady` in the wrong order.
|
|
|
|
std::lock_guard<std::mutex> DiagsLock(DiagnosticsMutex);
|
|
|
|
DocVersion &LastReportedDiagsVersion = ReportedDiagnosticVersions[File];
|
|
|
|
|
|
|
|
// FIXME(ibiryukov): get rid of '<' comparison here. In the current
|
|
|
|
// implementation diagnostics will not be reported after version counters'
|
|
|
|
// overflow. This should not happen in practice, since DocVersion is a
|
|
|
|
// 64-bit unsigned integer.
|
|
|
|
if (Version < LastReportedDiagsVersion)
|
|
|
|
return;
|
|
|
|
LastReportedDiagsVersion = Version;
|
|
|
|
|
|
|
|
DiagConsumer.onDiagnosticsReady(File, std::move(Diags));
|
2017-08-14 16:17:24 +08:00
|
|
|
}
|
2017-10-03 02:00:37 +08:00
|
|
|
|
|
|
|
void ClangdServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
|
|
|
|
// FIXME: Do nothing for now. This will be used for indexing and potentially
|
|
|
|
// invalidating other caches.
|
|
|
|
}
|
2018-01-25 22:32:21 +08:00
|
|
|
|
[clangd] Implementation of workspace/symbol request
Summary:
This is a basic implementation of the "workspace/symbol" request which is
used to find symbols by a string query. Since this is similar to code completion
in terms of result, this implementation reuses the "fuzzyFind" in order to get
matches. For now, the scoring algorithm is the same as code completion and
improvements could be done in the future.
The index model doesn't contain quite enough symbols for this to cover
common symbols like methods, enum class enumerators, functions in unamed
namespaces, etc. The index model will be augmented separately to achieve this.
Reviewers: sammccall, ilya-biryukov
Reviewed By: sammccall
Subscribers: jkorous, hokein, simark, sammccall, klimek, mgorny, ilya-biryukov, mgrang, jkorous-apple, ioeric, MaskRay, cfe-commits
Differential Revision: https://reviews.llvm.org/D44882
llvm-svn: 330637
2018-04-24 04:00:52 +08:00
|
|
|
void ClangdServer::workspaceSymbols(
|
|
|
|
StringRef Query, int Limit, Callback<std::vector<SymbolInformation>> CB) {
|
|
|
|
CB(clangd::getWorkspaceSymbols(Query, Limit, Index));
|
|
|
|
}
|
|
|
|
|
2018-01-25 22:32:21 +08:00
|
|
|
std::vector<std::pair<Path, std::size_t>>
|
|
|
|
ClangdServer::getUsedBytesPerFile() const {
|
2018-01-31 16:51:16 +08:00
|
|
|
return WorkScheduler.getUsedBytesPerFile();
|
2018-01-25 22:32:21 +08:00
|
|
|
}
|
2018-02-13 16:59:23 +08:00
|
|
|
|
|
|
|
LLVM_NODISCARD bool
|
|
|
|
ClangdServer::blockUntilIdleForTest(llvm::Optional<double> TimeoutSeconds) {
|
|
|
|
return WorkScheduler.blockUntilIdle(timeoutSeconds(TimeoutSeconds));
|
|
|
|
}
|