Index: clangd/ClangDMain.cpp =================================================================== --- clangd/ClangDMain.cpp +++ clangd/ClangDMain.cpp @@ -47,6 +47,9 @@ "textDocument/rangeFormatting", llvm::make_unique(Out, Store)); Dispatcher.registerHandler( + "textDocument/onTypeFormatting", + llvm::make_unique(Out, Store)); + Dispatcher.registerHandler( "textDocument/formatting", llvm::make_unique(Out, Store)); Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -144,6 +144,23 @@ parse(llvm::yaml::MappingNode *Params); }; +struct DocumentOnTypeFormattingParams { + /// The document to format. + TextDocumentIdentifier textDocument; + + /// The position at which this request was sent. + Position position; + + /// The character that has been typed. + std::string ch; + + /// The format options. + FormattingOptions options; + + static llvm::Optional + parse(llvm::yaml::MappingNode *Params); +}; + struct DocumentFormattingParams { /// The document to format. TextDocumentIdentifier textDocument; Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -378,6 +378,53 @@ return Result; } +llvm::Optional +DocumentOnTypeFormattingParams::parse(llvm::yaml::MappingNode *Params) { + DocumentOnTypeFormattingParams Result; + for (auto &NextKeyValue : *Params) { + auto *KeyString = dyn_cast(NextKeyValue.getKey()); + if (!KeyString) + return llvm::None; + + llvm::SmallString<10> KeyStorage; + StringRef KeyValue = KeyString->getValue(KeyStorage); + + if (KeyValue == "ch") { + auto *ScalarValue = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!ScalarValue) + return llvm::None; + llvm::SmallString<10> Storage; + Result.ch = ScalarValue->getValue(Storage); + continue; + } + + auto *Value = + dyn_cast_or_null(NextKeyValue.getValue()); + if (!Value) + return llvm::None; + if (KeyValue == "textDocument") { + auto Parsed = TextDocumentIdentifier::parse(Value); + if (!Parsed) + return llvm::None; + Result.textDocument = std::move(*Parsed); + } else if (KeyValue == "position") { + auto Parsed = Position::parse(Value); + if (!Parsed) + return llvm::None; + Result.position = std::move(*Parsed); + } else if (KeyValue == "options") { + auto Parsed = FormattingOptions::parse(Value); + if (!Parsed) + return llvm::None; + Result.options = std::move(*Parsed); + } else { + return llvm::None; + } + } + return Result; +} + llvm::Optional DocumentFormattingParams::parse(llvm::yaml::MappingNode *Params) { DocumentFormattingParams Result; Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -33,7 +33,8 @@ R"(,"result":{"capabilities":{ "textDocumentSync": 1, "documentFormattingProvider": true, - "documentRangeFormattingProvider": true + "documentRangeFormattingProvider": true, + "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]} }}})"); } }; @@ -71,6 +72,16 @@ DocumentStore &Store; }; +struct TextDocumentOnTypeFormattingHandler : Handler { + TextDocumentOnTypeFormattingHandler(JSONOutput &Output, DocumentStore &Store) + : Handler(Output), Store(Store) {} + + void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override; + +private: + DocumentStore &Store; +}; + struct TextDocumentRangeFormattingHandler : Handler { TextDocumentRangeFormattingHandler(JSONOutput &Output, DocumentStore &Store) : Handler(Output), Store(Store) {} Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -104,6 +104,27 @@ {clang::tooling::Range(Begin, Len)}, ID)); } +void TextDocumentOnTypeFormattingHandler::handleMethod( + llvm::yaml::MappingNode *Params, StringRef ID) { + auto DOTFP = DocumentOnTypeFormattingParams::parse(Params); + if (!DOTFP) { + Output.logs() << "Failed to decode DocumentOnTypeFormattingParams!\n"; + return; + } + + // Look for the previous opening brace from the character position and format + // starting from there. + std::string Code = Store.getDocument(DOTFP->textDocument.uri); + size_t CursorPos = positionToOffset(Code, DOTFP->position); + size_t PreviousLBracePos = StringRef(Code).find_last_of('{', CursorPos); + if (PreviousLBracePos == StringRef::npos) + PreviousLBracePos = CursorPos; + size_t Len = 1 + CursorPos - PreviousLBracePos; + + writeMessage(formatCode(Code, DOTFP->textDocument.uri, + {clang::tooling::Range(PreviousLBracePos, Len)}, ID)); +} + void TextDocumentFormattingHandler::handleMethod( llvm::yaml::MappingNode *Params, StringRef ID) { auto DFP = DocumentFormattingParams::parse(Params); Index: test/clangd/formatting.test =================================================================== --- test/clangd/formatting.test +++ test/clangd/formatting.test @@ -4,11 +4,12 @@ Content-Length: 125 {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} -# CHECK: Content-Length: 191 +# CHECK: Content-Length: 294 # CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{ # CHECK: "textDocumentSync": 1, # CHECK: "documentFormattingProvider": true, -# CHECK: "documentRangeFormattingProvider": true +# CHECK: "documentRangeFormattingProvider": true, +# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]} # CHECK: }}} # Content-Length: 193 @@ -48,6 +49,17 @@ {"jsonrpc":"2.0","id":4,"method":"textDocument/formatting","params":{"textDocument":{"uri":"file:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}} # CHECK: {"jsonrpc":"2.0","id":4,"result":[]} # +Content-Length: 193 + +{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///foo.c","version":5},"contentChanges":[{"text":"int foo ( int x ) {\n x = x + 1;\n return x;\n}"}]}} +# +# +Content-Length: 204 + +{"jsonrpc":"2.0","id":5,"method":"textDocument/onTypeFormatting","params":{"textDocument":{"uri":"file:///foo.c"},"position":{"line":3,"character":1},"ch":"}","options":{"tabSize":4,"insertSpaces":true}}} +# CHECK: {"jsonrpc":"2.0","id":5,"result":[{"range": {"start": {"line": 0, "character": 7}, "end": {"line": 0, "character": 8}}, "newText": ""},{"range": {"start": {"line": 0, "character": 9}, "end": {"line": 0, "character": 10}}, "newText": ""},{"range": {"start": {"line": 0, "character": 15}, "end": {"line": 0, "character": 16}}, "newText": ""}]} +# + Content-Length: 44 -{"jsonrpc":"2.0","id":5,"method":"shutdown"} +{"jsonrpc":"2.0","id":6,"method":"shutdown"}