Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -87,6 +87,20 @@ } // 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); + + if (auto TextDoc = Params.capabilities.textDocument) { + if (auto Completion = TextDoc->completion) { + if (auto CompletionItem = Completion->completionItem) { + if (CompletionItem->snippetSupport) + CCOpts.EnableSnippets = *CompletionItem->snippetSupport; + } + } + } + reply(json::obj{ {{"capabilities", json::obj{ @@ -116,10 +130,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. + llvm::Optional snippetSupport = false; + /// Client supports commit characters on a completion item. + llvm::Optional 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. + // llvm::Optional> documentationFormat; +}; +bool fromJSON(const json::Expr &, CompletionItemClientCapabilities &); + +struct CompletionClientCapabilities { + /// Whether completion supports dynamic registration. + llvm::Optional dynamicRegistration = false; + /// The client supports the following `CompletionItem` specific capabilities. + llvm::Optional 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. + llvm::Optional 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` + llvm::Optional 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. + llvm::Optional 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,29 @@ return false; } +bool fromJSON(const json::Expr &Params, CompletionItemClientCapabilities &R) { + json::ObjectMapper O(Params); + return O && O.map("snippetSupport", R.snippetSupport) && + O.map("commitCharacterSupport", R.commitCharacterSupport); +} + +bool fromJSON(const json::Expr &Params, CompletionClientCapabilities &R) { + json::ObjectMapper O(Params); + return O && O.map("dynamicRegistration", R.dynamicRegistration) && + O.map("completionItem", R.completionItem) && + O.map("contextSupport", R.contextSupport); +} + +bool fromJSON(const json::Expr &Params, TextDocumentClientCapabilities &R) { + json::ObjectMapper O(Params); + return O && O.map("completion", R.completion); +} + +bool fromJSON(const json::Expr &Params, ClientCapabilities &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument); +} + bool fromJSON(const json::Expr &Params, InitializeParams &R) { json::ObjectMapper O(Params); if (!O) @@ -159,6 +182,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 @@ -57,13 +57,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( @@ -239,7 +232,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-disabled.test =================================================================== --- /dev/null +++ test/clangd/completion-snippets-disabled.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": false + } + } + } + }, + "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", +# CHECK-NEXT: "insertTextFormat": 1, +# 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"} Index: test/clangd/completion-snippets-enabled.test =================================================================== --- /dev/null +++ test/clangd/completion-snippets-enabled.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"} Index: test/clangd/completion-snippets-missing.test =================================================================== --- /dev/null +++ test/clangd/completion-snippets-missing.test @@ -0,0 +1,35 @@ +# 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": {}, + "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", +# CHECK-NEXT: "insertTextFormat": 1, +# 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"}