forked from OSchip/llvm-project
[clangd] Add support for snippet completions
Enhances CompletionItemsCollector in such a way that snippet completions can be presented to the client. Enable snippet completion items by specifying -enable-snippets while invoking the clangd executable. See: https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#completion-request See: https://github.com/Microsoft/vscode/blob/master/src/vs/editor/contrib/snippet/browser/snippet.md Patch by Raoul Wols. llvm-svn: 313029
This commit is contained in:
parent
4f70b30d6f
commit
b33c15741b
|
@ -221,9 +221,11 @@ void ClangdLSPServer::LSPProtocolCallbacks::onGoToDefinition(
|
|||
}
|
||||
|
||||
ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
|
||||
bool SnippetCompletions,
|
||||
llvm::Optional<StringRef> ResourceDir)
|
||||
: Out(Out), DiagConsumer(*this),
|
||||
Server(CDB, DiagConsumer, FSProvider, AsyncThreadsCount, ResourceDir) {}
|
||||
Server(CDB, DiagConsumer, FSProvider, AsyncThreadsCount,
|
||||
SnippetCompletions, ResourceDir) {}
|
||||
|
||||
void ClangdLSPServer::run(std::istream &In) {
|
||||
assert(!IsDone && "Run was called before");
|
||||
|
|
|
@ -27,6 +27,7 @@ class JSONOutput;
|
|||
class ClangdLSPServer {
|
||||
public:
|
||||
ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
|
||||
bool SnippetCompletions,
|
||||
llvm::Optional<StringRef> ResourceDir);
|
||||
|
||||
/// Run LSP server loop, receiving input for it from \p In. \p In must be
|
||||
|
|
|
@ -144,12 +144,13 @@ ClangdScheduler::~ClangdScheduler() {
|
|||
ClangdServer::ClangdServer(GlobalCompilationDatabase &CDB,
|
||||
DiagnosticsConsumer &DiagConsumer,
|
||||
FileSystemProvider &FSProvider,
|
||||
unsigned AsyncThreadsCount,
|
||||
unsigned AsyncThreadsCount, bool SnippetCompletions,
|
||||
llvm::Optional<StringRef> ResourceDir)
|
||||
: CDB(CDB), DiagConsumer(DiagConsumer), FSProvider(FSProvider),
|
||||
ResourceDir(ResourceDir ? ResourceDir->str() : getStandardResourceDir()),
|
||||
PCHs(std::make_shared<PCHContainerOperations>()),
|
||||
WorkScheduler(AsyncThreadsCount) {}
|
||||
WorkScheduler(AsyncThreadsCount), SnippetCompletions(SnippetCompletions) {
|
||||
}
|
||||
|
||||
std::future<void> ClangdServer::addDocument(PathRef File, StringRef Contents) {
|
||||
DocVersion Version = DraftMgr.updateDraft(File, Contents);
|
||||
|
@ -206,10 +207,10 @@ ClangdServer::codeComplete(PathRef File, Position Pos,
|
|||
assert(Resources && "Calling completion on non-added file");
|
||||
|
||||
auto Preamble = Resources->getPossiblyStalePreamble();
|
||||
std::vector<CompletionItem> Result =
|
||||
clangd::codeComplete(File, Resources->getCompileCommand(),
|
||||
Preamble ? &Preamble->Preamble : nullptr,
|
||||
*OverridenContents, Pos, TaggedFS.Value, PCHs);
|
||||
std::vector<CompletionItem> Result = clangd::codeComplete(
|
||||
File, Resources->getCompileCommand(),
|
||||
Preamble ? &Preamble->Preamble : nullptr, *OverridenContents, Pos,
|
||||
TaggedFS.Value, PCHs, SnippetCompletions);
|
||||
return make_tagged(std::move(Result), TaggedFS.Tag);
|
||||
}
|
||||
|
||||
|
|
|
@ -179,6 +179,9 @@ public:
|
|||
/// AsyncThreadsCount worker threads. However, if \p AsyncThreadsCount is 0,
|
||||
/// all requests will be processed on the calling thread.
|
||||
///
|
||||
/// When \p SnippetCompletions is true, completion items will be presented
|
||||
/// with embedded snippets. Otherwise, plaintext items will be presented.
|
||||
///
|
||||
/// ClangdServer uses \p FSProvider to get an instance of vfs::FileSystem for
|
||||
/// each parsing request. Results of code completion and diagnostics also
|
||||
/// include a tag, that \p FSProvider returns along with the vfs::FileSystem.
|
||||
|
@ -201,6 +204,7 @@ public:
|
|||
ClangdServer(GlobalCompilationDatabase &CDB,
|
||||
DiagnosticsConsumer &DiagConsumer,
|
||||
FileSystemProvider &FSProvider, unsigned AsyncThreadsCount,
|
||||
bool SnippetCompletions,
|
||||
llvm::Optional<StringRef> ResourceDir = llvm::None);
|
||||
|
||||
/// Add a \p File to the list of tracked C++ files or update the contents if
|
||||
|
@ -274,6 +278,7 @@ private:
|
|||
// called before all other members to stop the worker thread that references
|
||||
// ClangdServer
|
||||
ClangdScheduler WorkScheduler;
|
||||
bool SnippetCompletions;
|
||||
};
|
||||
|
||||
} // namespace clangd
|
||||
|
|
|
@ -272,14 +272,22 @@ CompletionItemKind getKind(CXCursorKind K) {
|
|||
}
|
||||
}
|
||||
|
||||
std::string escapeSnippet(const llvm::StringRef Text) {
|
||||
std::string Result;
|
||||
Result.reserve(Text.size()); // Assume '$', '}' and '\\' are rare.
|
||||
for (const auto Character : Text) {
|
||||
if (Character == '$' || Character == '}' || Character == '\\')
|
||||
Result.push_back('\\');
|
||||
Result.push_back(Character);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
class CompletionItemsCollector : public CodeCompleteConsumer {
|
||||
std::vector<CompletionItem> *Items;
|
||||
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
|
||||
CodeCompletionTUInfo CCTUInfo;
|
||||
|
||||
public:
|
||||
CompletionItemsCollector(std::vector<CompletionItem> *Items,
|
||||
const CodeCompleteOptions &CodeCompleteOpts)
|
||||
CompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts,
|
||||
std::vector<CompletionItem> &Items)
|
||||
: CodeCompleteConsumer(CodeCompleteOpts, /*OutputIsBinary=*/false),
|
||||
Items(Items),
|
||||
Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
|
||||
|
@ -287,53 +295,237 @@ public:
|
|||
|
||||
void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
|
||||
CodeCompletionResult *Results,
|
||||
unsigned NumResults) override {
|
||||
for (unsigned I = 0; I != NumResults; ++I) {
|
||||
CodeCompletionResult &Result = Results[I];
|
||||
CodeCompletionString *CCS = Result.CreateCodeCompletionString(
|
||||
unsigned NumResults) override final {
|
||||
Items.reserve(NumResults);
|
||||
for (unsigned I = 0; I < NumResults; ++I) {
|
||||
auto &Result = Results[I];
|
||||
const auto *CCS = Result.CreateCodeCompletionString(
|
||||
S, Context, *Allocator, CCTUInfo,
|
||||
CodeCompleteOpts.IncludeBriefComments);
|
||||
if (CCS) {
|
||||
CompletionItem Item;
|
||||
for (CodeCompletionString::Chunk C : *CCS) {
|
||||
switch (C.Kind) {
|
||||
case CodeCompletionString::CK_ResultType:
|
||||
Item.detail = C.Text;
|
||||
break;
|
||||
case CodeCompletionString::CK_Optional:
|
||||
break;
|
||||
default:
|
||||
Item.label += C.Text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(CCS->getTypedText());
|
||||
Item.kind = getKind(Result.CursorKind);
|
||||
// Priority is a 16-bit integer, hence at most 5 digits.
|
||||
assert(CCS->getPriority() < 99999 && "Expecting code completion result "
|
||||
"priority to have at most "
|
||||
"5-digits");
|
||||
llvm::raw_string_ostream(Item.sortText)
|
||||
<< llvm::format("%05d%s", CCS->getPriority(), CCS->getTypedText());
|
||||
Item.insertText = Item.filterText = CCS->getTypedText();
|
||||
if (CCS->getBriefComment())
|
||||
Item.documentation = CCS->getBriefComment();
|
||||
Items->push_back(std::move(Item));
|
||||
}
|
||||
assert(CCS && "Expected the CodeCompletionString to be non-null");
|
||||
Items.push_back(ProcessCodeCompleteResult(Result, *CCS));
|
||||
}
|
||||
}
|
||||
|
||||
GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; }
|
||||
|
||||
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
|
||||
};
|
||||
|
||||
private:
|
||||
CompletionItem
|
||||
ProcessCodeCompleteResult(const CodeCompletionResult &Result,
|
||||
const CodeCompletionString &CCS) const {
|
||||
|
||||
// Adjust this to InsertTextFormat::Snippet iff we encounter a
|
||||
// CK_Placeholder chunk in SnippetCompletionItemsCollector.
|
||||
CompletionItem Item;
|
||||
Item.insertTextFormat = InsertTextFormat::PlainText;
|
||||
|
||||
FillDocumentation(CCS, Item);
|
||||
|
||||
// Fill in the label, detail, insertText and filterText fields of the
|
||||
// CompletionItem.
|
||||
ProcessChunks(CCS, Item);
|
||||
|
||||
// Fill in the kind field of the CompletionItem.
|
||||
Item.kind = getKind(Result.CursorKind);
|
||||
|
||||
FillSortText(CCS, Item);
|
||||
|
||||
return Item;
|
||||
}
|
||||
|
||||
virtual void ProcessChunks(const CodeCompletionString &CCS,
|
||||
CompletionItem &Item) const = 0;
|
||||
|
||||
void FillDocumentation(const CodeCompletionString &CCS,
|
||||
CompletionItem &Item) const {
|
||||
// Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this
|
||||
// information in the documentation field.
|
||||
const unsigned AnnotationCount = CCS.getAnnotationCount();
|
||||
if (AnnotationCount > 0) {
|
||||
Item.documentation += "Annotation";
|
||||
if (AnnotationCount == 1) {
|
||||
Item.documentation += ": ";
|
||||
} else /* AnnotationCount > 1 */ {
|
||||
Item.documentation += "s: ";
|
||||
}
|
||||
for (unsigned I = 0; I < AnnotationCount; ++I) {
|
||||
Item.documentation += CCS.getAnnotation(I);
|
||||
Item.documentation.push_back(I == AnnotationCount - 1 ? '\n' : ' ');
|
||||
}
|
||||
}
|
||||
|
||||
// Add brief documentation (if there is any).
|
||||
if (CCS.getBriefComment() != nullptr) {
|
||||
if (!Item.documentation.empty()) {
|
||||
// This means we previously added annotations. Add an extra newline
|
||||
// character to make the annotations stand out.
|
||||
Item.documentation.push_back('\n');
|
||||
}
|
||||
Item.documentation += CCS.getBriefComment();
|
||||
}
|
||||
}
|
||||
|
||||
void FillSortText(const CodeCompletionString &CCS,
|
||||
CompletionItem &Item) const {
|
||||
// Fill in the sortText of the CompletionItem.
|
||||
assert(CCS.getPriority() < 99999 && "Expecting code completion result "
|
||||
"priority to have at most 5-digits");
|
||||
llvm::raw_string_ostream(Item.sortText)
|
||||
<< llvm::format("%05d%s", CCS.getPriority(), Item.filterText.c_str());
|
||||
}
|
||||
|
||||
std::vector<CompletionItem> &Items;
|
||||
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
|
||||
CodeCompletionTUInfo CCTUInfo;
|
||||
|
||||
}; // CompletionItemsCollector
|
||||
|
||||
class PlainTextCompletionItemsCollector final
|
||||
: public CompletionItemsCollector {
|
||||
|
||||
public:
|
||||
PlainTextCompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts,
|
||||
std::vector<CompletionItem> &Items)
|
||||
: CompletionItemsCollector(CodeCompleteOpts, Items) {}
|
||||
|
||||
private:
|
||||
void ProcessChunks(const CodeCompletionString &CCS,
|
||||
CompletionItem &Item) const override {
|
||||
for (const auto &Chunk : CCS) {
|
||||
switch (Chunk.Kind) {
|
||||
case CodeCompletionString::CK_TypedText:
|
||||
// There's always exactly one CK_TypedText chunk.
|
||||
Item.insertText = Item.filterText = Chunk.Text;
|
||||
Item.label += Chunk.Text;
|
||||
break;
|
||||
case CodeCompletionString::CK_ResultType:
|
||||
assert(Item.detail.empty() && "Unexpected extraneous CK_ResultType");
|
||||
Item.detail = Chunk.Text;
|
||||
break;
|
||||
case CodeCompletionString::CK_Optional:
|
||||
break;
|
||||
default:
|
||||
Item.label += Chunk.Text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}; // PlainTextCompletionItemsCollector
|
||||
|
||||
class SnippetCompletionItemsCollector final : public CompletionItemsCollector {
|
||||
|
||||
public:
|
||||
SnippetCompletionItemsCollector(const CodeCompleteOptions &CodeCompleteOpts,
|
||||
std::vector<CompletionItem> &Items)
|
||||
: CompletionItemsCollector(CodeCompleteOpts, Items) {}
|
||||
|
||||
private:
|
||||
void ProcessChunks(const CodeCompletionString &CCS,
|
||||
CompletionItem &Item) const override {
|
||||
unsigned ArgCount = 0;
|
||||
for (const auto &Chunk : CCS) {
|
||||
switch (Chunk.Kind) {
|
||||
case CodeCompletionString::CK_TypedText:
|
||||
// The piece of text that the user is expected to type to match
|
||||
// the code-completion string, typically a keyword or the name of
|
||||
// a declarator or macro.
|
||||
Item.filterText = Chunk.Text;
|
||||
// Note intentional fallthrough here.
|
||||
case CodeCompletionString::CK_Text:
|
||||
// A piece of text that should be placed in the buffer,
|
||||
// e.g., parentheses or a comma in a function call.
|
||||
Item.label += Chunk.Text;
|
||||
Item.insertText += Chunk.Text;
|
||||
break;
|
||||
case CodeCompletionString::CK_Optional:
|
||||
// A code completion string that is entirely optional.
|
||||
// For example, an optional code completion string that
|
||||
// describes the default arguments in a function call.
|
||||
|
||||
// FIXME: Maybe add an option to allow presenting the optional chunks?
|
||||
break;
|
||||
case CodeCompletionString::CK_Placeholder:
|
||||
// A string that acts as a placeholder for, e.g., a function call
|
||||
// argument.
|
||||
++ArgCount;
|
||||
Item.insertText += "${" + std::to_string(ArgCount) + ':' +
|
||||
escapeSnippet(Chunk.Text) + '}';
|
||||
Item.label += Chunk.Text;
|
||||
Item.insertTextFormat = InsertTextFormat::Snippet;
|
||||
break;
|
||||
case CodeCompletionString::CK_Informative:
|
||||
// A piece of text that describes something about the result
|
||||
// but should not be inserted into the buffer.
|
||||
// For example, the word "const" for a const method, or the name of
|
||||
// the base class for methods that are part of the base class.
|
||||
Item.label += Chunk.Text;
|
||||
// Don't put the informative chunks in the insertText.
|
||||
break;
|
||||
case CodeCompletionString::CK_ResultType:
|
||||
// A piece of text that describes the type of an entity or,
|
||||
// for functions and methods, the return type.
|
||||
assert(Item.detail.empty() && "Unexpected extraneous CK_ResultType");
|
||||
Item.detail = Chunk.Text;
|
||||
break;
|
||||
case CodeCompletionString::CK_CurrentParameter:
|
||||
// A piece of text that describes the parameter that corresponds to
|
||||
// the code-completion location within a function call, message send,
|
||||
// macro invocation, etc.
|
||||
//
|
||||
// This should never be present while collecting completion items,
|
||||
// only while collecting overload candidates.
|
||||
llvm_unreachable("Unexpected CK_CurrentParameter while collecting "
|
||||
"CompletionItems");
|
||||
break;
|
||||
case CodeCompletionString::CK_LeftParen:
|
||||
// A left parenthesis ('(').
|
||||
case CodeCompletionString::CK_RightParen:
|
||||
// A right parenthesis (')').
|
||||
case CodeCompletionString::CK_LeftBracket:
|
||||
// A left bracket ('[').
|
||||
case CodeCompletionString::CK_RightBracket:
|
||||
// A right bracket (']').
|
||||
case CodeCompletionString::CK_LeftBrace:
|
||||
// A left brace ('{').
|
||||
case CodeCompletionString::CK_RightBrace:
|
||||
// A right brace ('}').
|
||||
case CodeCompletionString::CK_LeftAngle:
|
||||
// A left angle bracket ('<').
|
||||
case CodeCompletionString::CK_RightAngle:
|
||||
// A right angle bracket ('>').
|
||||
case CodeCompletionString::CK_Comma:
|
||||
// A comma separator (',').
|
||||
case CodeCompletionString::CK_Colon:
|
||||
// A colon (':').
|
||||
case CodeCompletionString::CK_SemiColon:
|
||||
// A semicolon (';').
|
||||
case CodeCompletionString::CK_Equal:
|
||||
// An '=' sign.
|
||||
case CodeCompletionString::CK_HorizontalSpace:
|
||||
// Horizontal whitespace (' ').
|
||||
Item.insertText += Chunk.Text;
|
||||
Item.label += Chunk.Text;
|
||||
break;
|
||||
case CodeCompletionString::CK_VerticalSpace:
|
||||
// Vertical whitespace ('\n' or '\r\n', depending on the
|
||||
// platform).
|
||||
Item.insertText += Chunk.Text;
|
||||
// Don't even add a space to the label.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}; // SnippetCompletionItemsCollector
|
||||
} // namespace
|
||||
|
||||
std::vector<CompletionItem>
|
||||
clangd::codeComplete(PathRef FileName, tooling::CompileCommand Command,
|
||||
PrecompiledPreamble const *Preamble, StringRef Contents,
|
||||
Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS,
|
||||
std::shared_ptr<PCHContainerOperations> PCHs) {
|
||||
std::shared_ptr<PCHContainerOperations> PCHs,
|
||||
bool SnippetCompletions) {
|
||||
std::vector<const char *> ArgStrs;
|
||||
for (const auto &S : Command.CommandLine)
|
||||
ArgStrs.push_back(S.c_str());
|
||||
|
@ -371,8 +563,6 @@ clangd::codeComplete(PathRef FileName, tooling::CompileCommand Command,
|
|||
FrontendOpts.SkipFunctionBodies = true;
|
||||
|
||||
FrontendOpts.CodeCompleteOpts.IncludeGlobals = true;
|
||||
// we don't handle code patterns properly yet, disable them.
|
||||
FrontendOpts.CodeCompleteOpts.IncludeCodePatterns = false;
|
||||
FrontendOpts.CodeCompleteOpts.IncludeMacros = true;
|
||||
FrontendOpts.CodeCompleteOpts.IncludeBriefComments = true;
|
||||
|
||||
|
@ -381,8 +571,15 @@ clangd::codeComplete(PathRef FileName, tooling::CompileCommand Command,
|
|||
FrontendOpts.CodeCompletionAt.Column = Pos.character + 1;
|
||||
|
||||
std::vector<CompletionItem> Items;
|
||||
Clang->setCodeCompletionConsumer(
|
||||
new CompletionItemsCollector(&Items, FrontendOpts.CodeCompleteOpts));
|
||||
if (SnippetCompletions) {
|
||||
FrontendOpts.CodeCompleteOpts.IncludeCodePatterns = true;
|
||||
Clang->setCodeCompletionConsumer(new SnippetCompletionItemsCollector(
|
||||
FrontendOpts.CodeCompleteOpts, Items));
|
||||
} else {
|
||||
FrontendOpts.CodeCompleteOpts.IncludeCodePatterns = false;
|
||||
Clang->setCodeCompletionConsumer(new PlainTextCompletionItemsCollector(
|
||||
FrontendOpts.CodeCompleteOpts, Items));
|
||||
}
|
||||
|
||||
SyntaxOnlyAction Action;
|
||||
if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) {
|
||||
|
|
|
@ -253,7 +253,8 @@ std::vector<CompletionItem>
|
|||
codeComplete(PathRef FileName, tooling::CompileCommand Command,
|
||||
PrecompiledPreamble const *Preamble, StringRef Contents,
|
||||
Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS,
|
||||
std::shared_ptr<PCHContainerOperations> PCHs);
|
||||
std::shared_ptr<PCHContainerOperations> PCHs,
|
||||
bool SnippetCompletions);
|
||||
|
||||
/// Get definition of symbol at a specified \p Pos.
|
||||
std::vector<Location> findDefinitions(ParsedAST &AST, Position Pos);
|
||||
|
|
|
@ -724,8 +724,8 @@ std::string CompletionItem::unparse(const CompletionItem &CI) {
|
|||
if (!CI.insertText.empty())
|
||||
Os << R"("insertText":")" << llvm::yaml::escape(CI.insertText) << R"(",)";
|
||||
if (CI.insertTextFormat != InsertTextFormat::Missing) {
|
||||
Os << R"("insertTextFormat":")" << static_cast<int>(CI.insertTextFormat)
|
||||
<< R"(",)";
|
||||
Os << R"("insertTextFormat":)" << static_cast<int>(CI.insertTextFormat)
|
||||
<< R"(,)";
|
||||
}
|
||||
if (CI.textEdit)
|
||||
Os << R"("textEdit":)" << TextEdit::unparse(*CI.textEdit) << ',';
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"version": "0.0.1",
|
||||
"publisher": "Unpublished",
|
||||
"engines": {
|
||||
"vscode": "^1.8.0"
|
||||
"vscode": "^1.15.0"
|
||||
},
|
||||
"categories": [
|
||||
"Languages",
|
||||
|
@ -24,8 +24,8 @@
|
|||
"test": "node ./node_modules/vscode/bin/test"
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-languageclient": "^2.6.3",
|
||||
"vscode-languageserver": "^2.6.2"
|
||||
"vscode-languageclient": "^3.3.0",
|
||||
"vscode-languageserver": "^3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^2.0.3",
|
||||
|
|
|
@ -42,7 +42,7 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
if (textEditor && textEditor.document.uri.toString(true) === uri) {
|
||||
textEditor.edit(mutator => {
|
||||
for (const edit of edits) {
|
||||
mutator.replace(vscodelc.Protocol2Code.asRange(edit.range), edit.newText);
|
||||
mutator.replace(clangdClient.protocol2CodeConverter.asRange(edit.range), edit.newText);
|
||||
}
|
||||
}).then((success) => {
|
||||
if (!success) {
|
||||
|
|
|
@ -26,6 +26,12 @@ static llvm::cl::opt<unsigned>
|
|||
llvm::cl::desc("Number of async workers used by clangd"),
|
||||
llvm::cl::init(getDefaultAsyncThreadsCount()));
|
||||
|
||||
static llvm::cl::opt<bool> EnableSnippets(
|
||||
"enable-snippets",
|
||||
llvm::cl::desc(
|
||||
"Present snippet completions instead of plaintext completions"),
|
||||
llvm::cl::init(false));
|
||||
|
||||
static llvm::cl::opt<bool> RunSynchronously(
|
||||
"run-synchronously",
|
||||
llvm::cl::desc("Parse on main thread. If set, -j is ignored"),
|
||||
|
@ -61,6 +67,7 @@ int main(int argc, char *argv[]) {
|
|||
if (!ResourceDir.empty())
|
||||
ResourceDirRef = ResourceDir;
|
||||
|
||||
ClangdLSPServer LSPServer(Out, WorkerThreadsCount, ResourceDirRef);
|
||||
ClangdLSPServer LSPServer(Out, WorkerThreadsCount, EnableSnippets,
|
||||
ResourceDirRef);
|
||||
LSPServer.run(std::cin);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ Content-Length: 146
|
|||
# Test authority-less URI
|
||||
#
|
||||
# CHECK: {"jsonrpc":"2.0","id":1,"result":[
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a"}
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a","insertTextFormat":1}
|
||||
# CHECK: ]}
|
||||
|
||||
Content-Length: 172
|
||||
|
@ -25,7 +25,7 @@ Content-Length: 172
|
|||
# Test params parsing in the presence of a 1.x-compatible client (inlined "uri")
|
||||
#
|
||||
# CHECK: {"jsonrpc":"2.0","id":2,"result":[
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a"}
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a","insertTextFormat":1}
|
||||
# CHECK: ]}
|
||||
Content-Length: 44
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
# RUN: clangd -run-synchronously -enable-snippets < %s | FileCheck %s
|
||||
# It is absolutely vital that this file has CRLF line endings.
|
||||
#
|
||||
Content-Length: 125
|
||||
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
|
||||
|
||||
Content-Length: 246
|
||||
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"struct fake { int a, bb, ccc; int f(int i, const float f) const; };\nint main() {\n fake f;\n f.\n}\n"}}}
|
||||
|
||||
Content-Length: 148
|
||||
|
||||
{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}}
|
||||
# The order of results returned by codeComplete seems to be
|
||||
# nondeterministic, so we check regardless of order.
|
||||
#
|
||||
# CHECK: {"jsonrpc":"2.0","id":1,"result":[
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"bb","kind":5,"detail":"int","sortText":"00035bb","filterText":"bb","insertText":"bb","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"ccc","kind":5,"detail":"int","sortText":"00035ccc","filterText":"ccc","insertText":"ccc","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"operator=(const fake &)","kind":2,"detail":"fake &","sortText":"00034operator=","filterText":"operator=","insertText":"operator=(${1:const fake &})","insertTextFormat":2}
|
||||
# CHECK-DAG: {"label":"~fake()","kind":4,"detail":"void","sortText":"00034~fake","filterText":"~fake","insertText":"~fake()","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"f(int i, const float f) const","kind":2,"detail":"int","sortText":"00035f","filterText":"f","insertText":"f(${1:int i}, ${2:const float f})","insertTextFormat":2}
|
||||
# CHECK: ]}
|
||||
Content-Length: 148
|
||||
|
||||
{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}}
|
||||
# Repeat the completion request, expect the same results.
|
||||
#
|
||||
# CHECK: {"jsonrpc":"2.0","id":2,"result":[
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"bb","kind":5,"detail":"int","sortText":"00035bb","filterText":"bb","insertText":"bb","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"ccc","kind":5,"detail":"int","sortText":"00035ccc","filterText":"ccc","insertText":"ccc","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"operator=(const fake &)","kind":2,"detail":"fake &","sortText":"00034operator=","filterText":"operator=","insertText":"operator=(${1:const fake &})","insertTextFormat":2}
|
||||
# CHECK-DAG: {"label":"~fake()","kind":4,"detail":"void","sortText":"00034~fake","filterText":"~fake","insertText":"~fake()","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"f(int i, const float f) const","kind":2,"detail":"int","sortText":"00035f","filterText":"f","insertText":"f(${1:int i}, ${2:const float f})","insertTextFormat":2}
|
||||
# CHECK: ]}
|
||||
# Update the source file and check for completions again.
|
||||
Content-Length: 226
|
||||
|
||||
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":2},"contentChanges":[{"text":"struct fancy { int (*func())(int, int); };\nint main() {\n fancy f;\n f.\n}\n"}]}}
|
||||
|
||||
Content-Length: 148
|
||||
|
||||
{"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}}
|
||||
# Repeat the completion request, expect the same results.
|
||||
#
|
||||
# CHECK: {"jsonrpc":"2.0","id":3,"result":[
|
||||
# CHECK-DAG: {"label":"func()","kind":2,"detail":"int (*)(int, int)","sortText":"00034func","filterText":"func","insertText":"func()","insertTextFormat":1}
|
||||
# CHECK: ]}
|
||||
Content-Length: 44
|
||||
|
||||
{"jsonrpc":"2.0","id":4,"method":"shutdown"}
|
|
@ -16,12 +16,12 @@ Content-Length: 148
|
|||
# nondeterministic, so we check regardless of order.
|
||||
#
|
||||
# CHECK: {"jsonrpc":"2.0","id":1,"result":[
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a"}
|
||||
# CHECK-DAG: {"label":"bb","kind":5,"detail":"int","sortText":"00035bb","filterText":"bb","insertText":"bb"}
|
||||
# CHECK-DAG: {"label":"ccc","kind":5,"detail":"int","sortText":"00035ccc","filterText":"ccc","insertText":"ccc"}
|
||||
# CHECK-DAG: {"label":"operator=(const fake &)","kind":2,"detail":"fake &","sortText":"00034operator=","filterText":"operator=","insertText":"operator="}
|
||||
# CHECK-DAG: {"label":"~fake()","kind":4,"detail":"void","sortText":"00034~fake","filterText":"~fake","insertText":"~fake"}
|
||||
# CHECK-DAG: {"label":"f(int i, const float f) const","kind":2,"detail":"int","sortText":"00035f","filterText":"f","insertText":"f"}
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"bb","kind":5,"detail":"int","sortText":"00035bb","filterText":"bb","insertText":"bb","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"ccc","kind":5,"detail":"int","sortText":"00035ccc","filterText":"ccc","insertText":"ccc","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"operator=(const fake &)","kind":2,"detail":"fake &","sortText":"00034operator=","filterText":"operator=","insertText":"operator=","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"~fake()","kind":4,"detail":"void","sortText":"00034~fake","filterText":"~fake","insertText":"~fake","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"f(int i, const float f) const","kind":2,"detail":"int","sortText":"00035f","filterText":"f","insertText":"f","insertTextFormat":1}
|
||||
# CHECK: ]}
|
||||
Content-Length: 148
|
||||
|
||||
|
@ -29,12 +29,12 @@ Content-Length: 148
|
|||
# Repeat the completion request, expect the same results.
|
||||
#
|
||||
# CHECK: {"jsonrpc":"2.0","id":2,"result":[
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a"}
|
||||
# CHECK-DAG: {"label":"bb","kind":5,"detail":"int","sortText":"00035bb","filterText":"bb","insertText":"bb"}
|
||||
# CHECK-DAG: {"label":"ccc","kind":5,"detail":"int","sortText":"00035ccc","filterText":"ccc","insertText":"ccc"}
|
||||
# CHECK-DAG: {"label":"operator=(const fake &)","kind":2,"detail":"fake &","sortText":"00034operator=","filterText":"operator=","insertText":"operator="}
|
||||
# CHECK-DAG: {"label":"~fake()","kind":4,"detail":"void","sortText":"00034~fake","filterText":"~fake","insertText":"~fake"}
|
||||
# CHECK-DAG: {"label":"f(int i, const float f) const","kind":2,"detail":"int","sortText":"00035f","filterText":"f","insertText":"f"}
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"bb","kind":5,"detail":"int","sortText":"00035bb","filterText":"bb","insertText":"bb","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"ccc","kind":5,"detail":"int","sortText":"00035ccc","filterText":"ccc","insertText":"ccc","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"operator=(const fake &)","kind":2,"detail":"fake &","sortText":"00034operator=","filterText":"operator=","insertText":"operator=","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"~fake()","kind":4,"detail":"void","sortText":"00034~fake","filterText":"~fake","insertText":"~fake","insertTextFormat":1}
|
||||
# CHECK-DAG: {"label":"f(int i, const float f) const","kind":2,"detail":"int","sortText":"00035f","filterText":"f","insertText":"f","insertTextFormat":1}
|
||||
# CHECK: ]}
|
||||
# Update the source file and check for completions again.
|
||||
Content-Length: 226
|
||||
|
@ -47,7 +47,7 @@ Content-Length: 148
|
|||
# Repeat the completion request, expect the same results.
|
||||
#
|
||||
# CHECK: {"jsonrpc":"2.0","id":3,"result":[
|
||||
# CHECK-DAG: {"label":"func()","kind":2,"detail":"int (*)(int, int)","sortText":"00034func","filterText":"func","insertText":"func"}
|
||||
# CHECK-DAG: {"label":"func()","kind":2,"detail":"int (*)(int, int)","sortText":"00034func","filterText":"func","insertText":"func","insertTextFormat":1}
|
||||
# CHECK: ]}
|
||||
Content-Length: 44
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ Content-Length: 146
|
|||
# Test message with Content-Type before Content-Length
|
||||
#
|
||||
# CHECK: {"jsonrpc":"2.0","id":1,"result":[
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a"}
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a","insertTextFormat":1}
|
||||
# CHECK: ]}
|
||||
|
||||
X-Test: Testing
|
||||
|
@ -50,7 +50,7 @@ Content-Length: 146
|
|||
# Test message with duplicate Content-Length headers
|
||||
#
|
||||
# CHECK: {"jsonrpc":"2.0","id":3,"result":[
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a"}
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a","insertTextFormat":1}
|
||||
# CHECK: ]}
|
||||
# STDERR: Warning: Duplicate Content-Length header received. The previous value for this message (10) was ignored.
|
||||
|
||||
|
@ -69,7 +69,7 @@ Content-Length: 146
|
|||
# Test message with Content-Type before Content-Length
|
||||
#
|
||||
# CHECK: {"jsonrpc":"2.0","id":5,"result":[
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a"}
|
||||
# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"00035a","filterText":"a","insertText":"a","insertTextFormat":1}
|
||||
# CHECK: ]}
|
||||
|
||||
Content-Length: 1024
|
||||
|
|
|
@ -301,7 +301,8 @@ protected:
|
|||
MockFSProvider FS;
|
||||
ErrorCheckingDiagConsumer DiagConsumer;
|
||||
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
|
||||
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount());
|
||||
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
|
||||
/*SnippetCompletions=*/false);
|
||||
for (const auto &FileWithContents : ExtraFiles)
|
||||
FS.Files[getVirtualTestFilePath(FileWithContents.first)] =
|
||||
FileWithContents.second;
|
||||
|
@ -363,7 +364,8 @@ TEST_F(ClangdVFSTest, Reparse) {
|
|||
MockFSProvider FS;
|
||||
ErrorCheckingDiagConsumer DiagConsumer;
|
||||
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
|
||||
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount());
|
||||
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
|
||||
/*SnippetCompletions=*/false);
|
||||
|
||||
const auto SourceContents = R"cpp(
|
||||
#include "foo.h"
|
||||
|
@ -407,7 +409,8 @@ TEST_F(ClangdVFSTest, ReparseOnHeaderChange) {
|
|||
ErrorCheckingDiagConsumer DiagConsumer;
|
||||
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
|
||||
|
||||
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount());
|
||||
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
|
||||
/*SnippetCompletions=*/false);
|
||||
|
||||
const auto SourceContents = R"cpp(
|
||||
#include "foo.h"
|
||||
|
@ -454,7 +457,7 @@ TEST_F(ClangdVFSTest, CheckVersions) {
|
|||
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
|
||||
// Run ClangdServer synchronously.
|
||||
ClangdServer Server(CDB, DiagConsumer, FS,
|
||||
/*AsyncThreadsCount=*/0);
|
||||
/*AsyncThreadsCount=*/0, /*SnippetCompletions=*/false);
|
||||
|
||||
auto FooCpp = getVirtualTestFilePath("foo.cpp");
|
||||
const auto SourceContents = "int a;";
|
||||
|
@ -487,7 +490,7 @@ TEST_F(ClangdVFSTest, SearchLibDir) {
|
|||
"-stdlib=libstdc++"});
|
||||
// Run ClangdServer synchronously.
|
||||
ClangdServer Server(CDB, DiagConsumer, FS,
|
||||
/*AsyncThreadsCount=*/0);
|
||||
/*AsyncThreadsCount=*/0, /*SnippetCompletions=*/false);
|
||||
|
||||
// Just a random gcc version string
|
||||
SmallString<8> Version("4.9.3");
|
||||
|
@ -535,7 +538,7 @@ TEST_F(ClangdVFSTest, ForceReparseCompileCommand) {
|
|||
ErrorCheckingDiagConsumer DiagConsumer;
|
||||
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
|
||||
ClangdServer Server(CDB, DiagConsumer, FS,
|
||||
/*AsyncThreadsCount=*/0);
|
||||
/*AsyncThreadsCount=*/0, /*SnippetCompletions=*/false);
|
||||
// No need to sync reparses, because reparses are performed on the calling
|
||||
// thread to true.
|
||||
|
||||
|
@ -593,7 +596,8 @@ TEST_F(ClangdCompletionTest, CheckContentsOverride) {
|
|||
ErrorCheckingDiagConsumer DiagConsumer;
|
||||
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
|
||||
|
||||
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount());
|
||||
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
|
||||
/*SnippetCompletions=*/false);
|
||||
|
||||
auto FooCpp = getVirtualTestFilePath("foo.cpp");
|
||||
const auto SourceContents = R"cpp(
|
||||
|
@ -740,7 +744,8 @@ int d;
|
|||
TestDiagConsumer DiagConsumer;
|
||||
{
|
||||
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
|
||||
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount());
|
||||
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
|
||||
/*SnippetCompletions=*/false);
|
||||
|
||||
// Prepare some random distributions for the test.
|
||||
std::random_device RandGen;
|
||||
|
|
Loading…
Reference in New Issue