Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -87,6 +87,14 @@ } // namespace void ClangdLSPServer::onInitialize(InitializeParams &Params) { + if (Params.rootUri && !Params.rootUri->file.empty()) + Server.setRootPath(Params.rootUri->file); + else if (Params.rootPath && !Params.rootPath->empty()) + Server.setRootPath(*Params.rootPath); + + CCOpts.EnableSnippets = + Params.capabilities.textDocument.completion.completionItem.snippetSupport; + reply(json::obj{ {{"capabilities", json::obj{ @@ -116,10 +124,6 @@ {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}}, }}, }}}}); - if (Params.rootUri && !Params.rootUri->file.empty()) - Server.setRootPath(Params.rootUri->file); - else if (Params.rootPath && !Params.rootPath->empty()) - Server.setRootPath(*Params.rootPath); } void ClangdLSPServer::onShutdown(ShutdownParams &Params) { Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -179,6 +179,51 @@ using ShutdownParams = NoParams; using ExitParams = NoParams; +struct CompletionItemClientCapabilities { + /// Client supports snippets as insert text. + bool snippetSupport = false; + /// Client supports commit characters on a completion item. + bool commitCharacterSupport = false; + // Client supports the follow content formats for the documentation property. + // The order describes the preferred format of the client. + // NOTE: not used by clangd at the moment. + // std::vector documentationFormat; +}; +bool fromJSON(const json::Expr &, CompletionItemClientCapabilities &); + +struct CompletionClientCapabilities { + /// Whether completion supports dynamic registration. + bool dynamicRegistration = false; + /// The client supports the following `CompletionItem` specific capabilities. + CompletionItemClientCapabilities completionItem; + // NOTE: not used by clangd at the moment. + // llvm::Optional completionItemKind; + + /// The client supports to send additional context information for a + /// `textDocument/completion` request. + bool contextSupport = false; +}; +bool fromJSON(const json::Expr &, CompletionClientCapabilities &); + +// FIXME: most of the capabilities are missing from this struct. Only the ones +// used by clangd are currently there. +struct TextDocumentClientCapabilities { + /// Capabilities specific to the `textDocument/completion` + CompletionClientCapabilities completion; +}; +bool fromJSON(const json::Expr &, TextDocumentClientCapabilities &); + +struct ClientCapabilities { + // Workspace specific client capabilities. + // NOTE: not used by clangd at the moment. + // WorkspaceClientCapabilities workspace; + + // Text document specific client capabilities. + TextDocumentClientCapabilities textDocument; +}; + +bool fromJSON(const json::Expr &, ClientCapabilities &); + struct InitializeParams { /// The process Id of the parent process that started /// the server. Is null if the process has not been started by another @@ -201,8 +246,7 @@ // initializationOptions?: any; /// The capabilities provided by the client (editor or tool) - /// Note: Not currently used by clangd - // ClientCapabilities capabilities; + ClientCapabilities capabilities; /// The initial trace setting. If omitted trace is disabled ('off'). llvm::Optional trace; Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -150,6 +150,41 @@ return false; } +bool fromJSON(const json::Expr &Params, CompletionItemClientCapabilities &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + O.map("snippetSupport", R.snippetSupport); + O.map("commitCharacterSupport", R.commitCharacterSupport); + return true; +} + +bool fromJSON(const json::Expr &Params, CompletionClientCapabilities &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + O.map("dynamicRegistration", R.dynamicRegistration); + O.map("completionItem", R.completionItem); + O.map("contextSupport", R.contextSupport); + return true; +} + +bool fromJSON(const json::Expr &Params, TextDocumentClientCapabilities &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + O.map("completion", R.completion); + return true; +} + +bool fromJSON(const json::Expr &Params, ClientCapabilities &R) { + json::ObjectMapper O(Params); + if (!O) + return false; + O.map("textDocument", R.textDocument); + return true; +} + bool fromJSON(const json::Expr &Params, InitializeParams &R) { json::ObjectMapper O(Params); if (!O) @@ -159,6 +194,7 @@ O.map("processId", R.processId); O.map("rootUri", R.rootUri); O.map("rootPath", R.rootPath); + O.map("capabilities", R.capabilities); O.map("trace", R.trace); // initializationOptions, capabilities unused return true; Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -58,13 +58,6 @@ llvm::cl::desc("Number of async workers used by clangd"), llvm::cl::init(getDefaultAsyncThreadsCount())); -static llvm::cl::opt EnableSnippets( - "enable-snippets", - llvm::cl::desc( - "Present snippet completions instead of plaintext completions. " - "This also enables code pattern results." /* FIXME: should it? */), - llvm::cl::init(clangd::CodeCompleteOptions().EnableSnippets)); - // FIXME: Flags are the wrong mechanism for user preferences. // We should probably read a dotfile or similar. static llvm::cl::opt IncludeIneligibleResults( @@ -237,7 +230,6 @@ if (EnableIndexBasedCompletion && !YamlSymbolFile.empty()) StaticIdx = BuildStaticIndex(YamlSymbolFile); clangd::CodeCompleteOptions CCOpts; - CCOpts.EnableSnippets = EnableSnippets; CCOpts.IncludeIneligibleResults = IncludeIneligibleResults; CCOpts.Limit = LimitCompletionResult; // Initialize and run ClangdLSPServer. Index: test/clangd/completion-snippets.test =================================================================== --- /dev/null +++ test/clangd/completion-snippets.test @@ -0,0 +1,43 @@ +# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace %s +# RUN: clangd -lit-test -pch-storage=memory < %s | FileCheck -strict-whitespace %s +{ + "jsonrpc": "2.0", + "id": 0, + "method": "initialize", + "params": { + "processId": 123, + "rootPath": "clangd", + "capabilities": { + "textDocument": { + "completion": { + "completionItem": { + "snippetSupport": true + } + } + } + }, + "trace": "off" + } +} +--- +{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"test:///main.cpp","languageId":"cpp","version":1,"text":"int func_with_args(int a, int b);\nint main() {\nfunc_with\n}"}}} +--- +{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"test:///main.cpp"},"position":{"line":2,"character":7}}} +# CHECK: "id": 1 +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": { +# CHECK-NEXT: "isIncomplete": {{.*}} +# CHECK-NEXT: "items": [ +# CHECK-NEXT: { +# CHECK-NEXT: "detail": "int", +# CHECK-NEXT: "filterText": "func_with_args", +# CHECK-NEXT: "insertText": "func_with_args(${1:int a}, ${2:int b})", +# CHECK-NEXT: "insertTextFormat": 2, +# CHECK-NEXT: "kind": 3, +# CHECK-NEXT: "label": "func_with_args(int a, int b)", +# CHECK-NEXT: "sortText": "{{.*}}func_with_args" +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT: } +--- +{"jsonrpc":"2.0","id":4,"method":"shutdown"}