[PATCH] D48559: [clangd] refactoring for XPC transport layer [NFCI]
jkorous added a comment. Hi, we decided to go with a different solution: https://reviews.llvm.org/D50452 Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D48559 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D48559: [clangd] refactoring for XPC transport layer [NFCI]
jkorous planned changes to this revision. jkorous added a comment. Hi Sam, we are still discussing internally how to fit clangd and XPC together. Please bear with us and ignore our patches until we decide. Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D48559 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D48559: [clangd] refactoring for XPC transport layer [NFCI]
arphaman added a comment. In https://reviews.llvm.org/D48559#1169975, @sammccall wrote: > Sorry for the delay here, and I'm sorry I haven't been into the patches in > detail again yet - crazy week. After experiments, I am convinced the > Transport abstraction patch can turn into something nice **if** we want XPC > vs JSON to be a pure transport-level difference with the same semantics. But > you need to decide the approach here based on your requirements. > > In https://reviews.llvm.org/D48559#1168204, @jkorous wrote: > > > Sam, just out of curiosity - would it be possible for you to share any > > relevant experience gained by using porting clangd to protobuf-based > > transport layer? > > > Sure! (and sorry for the delay here). > > This is done as part of an internal-only editor that's a hosted web > application with tight integration to our internal source control, build > system etc. > That's not my immediate team, but we talk a lot and collaborate on the > integration, which is different than a typical LSP server/editor integration > in a few ways. > (I expect some of these aspects will apply to XCode+clangd, and others > won't). This is kind of a brain dump... > > - like a regular LSP client, the editor wants to run clangd out-of-process > for isolation reasons, and because of cross-language issues. Because of our > hosted datacenter environment, a network RPC server was the most obvious > choice, and protobufs are our lingua franca for such servers. This sounds > analagous to the choice of XPC to me. > - the editor is multi-language, so the idea of using the existing LSP is > appealing (reuse servers, we can't rewrite the world). > - adapting to protobuf is some work (write a wrapper for each LSP server). In > principle a generic one is possible, but not for us due to VFS needs etc. > - you can wrap at the LSP level (CompletionList -> CompletionListProto) or at > the transport level (json::Value --> JsonValueProto). Editor team team went > for the former. > - consuming clangd as a C++ API rather than as an external process or opaque > JSON stream, gives lots more flexibility, and we can add extension points > relatively easily. Robust VFS support, performance tracing, logging, > different threading model are examples of this outside the scope of LSP. The > internal code uses unmodified upstream clangd code, but provides its own > main(). > - Inside the scope of LSP, changes are tempting too due to LSP limitations. > Since the editor team controls the protocol and the frontend, they can add > features. This is a source of tension: we (clangd folks) don't want to > support two divergent APIs. So in **limited** cases we added to clangd a > richer/more semantic C++ API that provides extra features over > more-presentational LSP. Best examples are diagnostics (C++ API includes > fixits and 'note' stack, before LSP supported these), and code completion > (C++ API includes semantic things like "name of include to insert"). We've > tried to keep this limited to what's both valuable and sensible. > - adding LSP extensions to the *protocol* causes skew across languages. So > the divergences at that level tend to be more presentational (e.g. > "completion item documentation subtitle"). The clangd-wrapper decides how to > map the semantic Clangd C++ API fields onto the presentational mutant-LSP > fields, in the same way that regular clangd decides how to map them onto > regular LSP. > - it's annoying that the issues you run into with this setup need to be > carefully dissected (editor vs wrapper vs clangd) to work out where the bug > belongs. Compare to "plain" clangd where you can just check with a different > editor and then know the bug is in clangd. > > In summary: > - speaking a different wire protocol that's semantically identical to LSP > is an easy win > - owning a separate `main()` so you can plug in cross-cutting facilities > (logging, tracing, index) is very manageable and worthwhile > - maintaining a separate protocol is a bunch more work and decisions all > the time, even if divergences from LSP are small. It *is* more flexible > though. > > One halfway possibility if you're on the fence: we could support some > limited extensions to the protocol behind an option (e.g. extra fields > serialized with the option, whether XPC or JSON is used). It's less > flexibility than full ownership of the protocol semantics on Apple's side > though. A custom protocol does sound quite appealing. I previously was quite skeptical just because ClangdLSPServer seemed to have had a lot of important logic, but it seems that that's not the case right now. I will lead an internal discussion on this to figure out if that's something we'd like to do. I'll respond here sometime next week. Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D48559 ___ cfe-commits mailing list
[PATCH] D48559: [clangd] refactoring for XPC transport layer [NFCI]
sammccall added a comment. Sorry for the delay here, and I'm sorry I haven't been into the patches in detail again yet - crazy week. After experiments, I am convinced the Transport abstraction patch can turn into something nice **if** we want XPC vs JSON to be a pure transport-level difference with the same semantics. But you need to decide the approach here based on your requirements. In https://reviews.llvm.org/D48559#1168204, @jkorous wrote: > Sam, just out of curiosity - would it be possible for you to share any > relevant experience gained by using porting clangd to protobuf-based > transport layer? Sure! (and sorry for the delay here). This is done as part of an internal-only editor that's a hosted web application with tight integration to our internal source control, build system etc. That's not my immediate team, but we talk a lot and collaborate on the integration, which is different than a typical LSP server/editor integration in a few ways. (I expect some of these aspects will apply to XCode+clangd, and others won't). This is kind of a brain dump... - like a regular LSP client, the editor wants to run clangd out-of-process for isolation reasons, and because of cross-language issues. Because of our hosted datacenter environment, a network RPC server was the most obvious choice, and protobufs are our lingua franca for such servers. This sounds analagous to the choice of XPC to me. - the editor is multi-language, so the idea of using the existing LSP is appealing (reuse servers, we can't rewrite the world). - adapting to protobuf is some work (write a wrapper for each LSP server). In principle a generic one is possible, but not for us due to VFS needs etc. - you can wrap at the LSP level (CompletionList -> CompletionListProto) or at the transport level (json::Value --> JsonValueProto). Editor team team went for the former. - consuming clangd as a C++ API rather than as an external process or opaque JSON stream, gives lots more flexibility, and we can add extension points relatively easily. Robust VFS support, performance tracing, logging, different threading model are examples of this outside the scope of LSP. The internal code uses unmodified upstream clangd code, but provides its own main(). - Inside the scope of LSP, changes are tempting too due to LSP limitations. Since the editor team controls the protocol and the frontend, they can add features. This is a source of tension: we (clangd folks) don't want to support two divergent APIs. So in **limited** cases we added to clangd a richer/more semantic C++ API that provides extra features over more-presentational LSP. Best examples are diagnostics (C++ API includes fixits and 'note' stack, before LSP supported these), and code completion (C++ API includes semantic things like "name of include to insert"). We've tried to keep this limited to what's both valuable and sensible. - adding LSP extensions to the *protocol* causes skew across languages. So the divergences at that level tend to be more presentational (e.g. "completion item documentation subtitle"). The clangd-wrapper decides how to map the semantic Clangd C++ API fields onto the presentational mutant-LSP fields, in the same way that regular clangd decides how to map them onto regular LSP. - it's annoying that the issues you run into with this setup need to be carefully dissected (editor vs wrapper vs clangd) to work out where the bug belongs. Compare to "plain" clangd where you can just check with a different editor and then know the bug is in clangd. In summary: - speaking a different wire protocol that's semantically identical to LSP is an easy win - owning a separate `main()` so you can plug in cross-cutting facilities (logging, tracing, index) is very manageable and worthwhile - maintaining a separate protocol is a bunch more work and decisions all the time, even if divergences from LSP are small. It *is* more flexible though. One halfway possibility if you're on the fence: we could support some limited extensions to the protocol behind an option (e.g. extra fields serialized with the option, whether XPC or JSON is used). It's less flexibility than full ownership of the protocol semantics on Apple's side though. Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D48559 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D48559: [clangd] refactoring for XPC transport layer [NFCI]
jkorous added a comment. BTW Just for the record - I put the rebased & updated patch here: [clangd] XPC WIP https://reviews.llvm.org/D49548 Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D48559 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D48559: [clangd] refactoring for XPC transport layer [NFCI]
jkorous added a comment. Hi Sam, we discussed a bit more with Alex offline. Ultimately we don't mind using JSON as part of the generic interface but we'd like to optimize specific cases in the future (thinking only about code completion so far) which might need to "work-around" the abstraction. What is your opinion about this - does it ruin the whole point of having the abstraction or do you consider pragmatic to have abstraction covering most of the functionality with maybe couple exceptions? I kind of assume (please correct me if I am wrong) that you might have done something similar with profobuf interface so we're definitely happy to discuss. Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D48559 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D48559: [clangd] refactoring for XPC transport layer [NFCI]
jkorous added a comment. Sam, just out of curiosity - would it be possible for you to share any relevant experience gained by using porting clangd to protobuf-based transport layer? Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D48559 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D48559: [clangd] refactoring for XPC transport layer [NFCI]
arphaman added a comment. In https://reviews.llvm.org/D48559#1165511, @sammccall wrote: > In https://reviews.llvm.org/D48559#1165172, @jkorous wrote: > > > Hi Sam, thanks for your feedback! > > > > In general I agree that explicitly factoring out the transport layer and > > improving layering would be a good thing. Unfortunately it's highly > > probable that we'd like to drop JSON completely from XPC dispatch (XPC -> > > JSON -> ProtocolCallbacks -> JSON -> XPC) in not too distant future. > > (Please don't take this as a promise as our priorities might change but > > it's probable enough that I don't recommend to base any common abstraction > > on JSON.) I originally tried to create similar abstraction but ultimately > > gave up as it was way too complex. Not saying it's impossible or that I > > would not like to though! > > > That makes sense and is good to know. I definitely don't want to add a > transport abstraction that isn't a good fit for your long-term plans. > > I think we need to clearly see what the goal is to get the design right > though - it doesn't make sense to refactor in order to share code > temporarily. Let's take code completion as an example, as it's one of the > ones we've fleshed out most for our internal (non-LSP) clients. > > - if the goal is to mirror JSON-RPC with a different encoding (current > patches), and you're just concerned about the *efficiency* of > `CodeCompletion` -> `CompletionItem` -> `json::Value` -> `xpc_object_t`, this > seems like premature optimization to me, but let's discuss! > - if the goal is to basically follow the LSP (same textDocument/completion > for), using the `CompletionItem` structs in `Protocol.h` but exposing > more/fewer fields, renaming them etc, then we should probably have the > feature code express replies as protocol structs, and make the transport > define a way to transform specific objects (CompletionItem) into generic ones > whose type (json::Value or xpc_object_t) are transport-specific > - if the goal is to maintain a stable protocol that can be evolved > independently of LSP (different methods or semantics) then this isn't really > a transport, it's a new protocol. I think the best option is probably to skip > `ClangdLSPServer` and have the xpc service wrap `ClangdServer` instead, with > a separate main entrypoint. The litmus test here: as people make changes to > clangd that affect the LSP surface, do you want the XPC service's interface > to change too? This is the hardest model to support as the C++ API > (ClangdServer) is not a stable one, but it's what we do for the other non-LSP > clients. > > Does one of these seem close to what you want? Any thoughts on the > conclusions? Hi Sam, Thanks for your feedback! Generally right now will be mirroring JSON-RPC with a different encoding, but in the future we will send some optimized payloads for a subset of replies which are constructed out of things like `CompletionItem` directly (so bypassing the JSON step). I believe that your patch (https://reviews.llvm.org/D49389) is implementing the JSON-RPC with a choice of encoding, which will work for us right now, and it doesn't look like we will have problems optimizing our payloads later when we choose to do so. However, the second option that you proposed does sound quite compelling as well (i.e. `then we should probably have the feature code express replies as protocol structs`), as it would give us more flexibility. However, I think it's orthogonal to the first option (JSON-RPC with a choice of encoding), so maybe we can leave it as something that will be on the table if we need it later? I think for the transport layer idea you proposed in https://reviews.llvm.org/D49389 makes sense and will work for us, and in the future we always can implement the second option that your proposed on top of of your patch, or use a simpler way to optimize our payloads without much disruption. Thanks, Alex Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D48559 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D48559: [clangd] refactoring for XPC transport layer [NFCI]
sammccall added a comment. In https://reviews.llvm.org/D48559#1165172, @jkorous wrote: > Hi Sam, thanks for your feedback! > > In general I agree that explicitly factoring out the transport layer and > improving layering would be a good thing. Unfortunately it's highly probable > that we'd like to drop JSON completely from XPC dispatch (XPC -> JSON -> > ProtocolCallbacks -> JSON -> XPC) in not too distant future. (Please don't > take this as a promise as our priorities might change but it's probable > enough that I don't recommend to base any common abstraction on JSON.) I > originally tried to create similar abstraction but ultimately gave up as it > was way too complex. Not saying it's impossible or that I would not like to > though! That makes sense and is good to know. I definitely don't want to add a transport abstraction that isn't a good fit for your long-term plans. I think we need to clearly see what the goal is to get the design right though - it doesn't make sense to refactor in order to share code temporarily. Let's take code completion as an example, as it's one of the ones we've fleshed out most for our internal (non-LSP) clients. - if the goal is to mirror JSON-RPC with a different encoding (current patches), and you're just concerned about the *efficiency* of `CodeCompletion` -> `CompletionItem` -> `json::Value` -> `xpc_object_t`, this seems like premature optimization to me, but let's discuss! - if the goal is to basically follow the LSP (same textDocument/completion for), using the `CompletionItem` structs in `Protocol.h` but exposing more/fewer fields, renaming them etc, then we should probably have the feature code express replies as protocol structs, and make the transport define a way to transform specific objects (CompletionItem) into generic ones whose type (json::Value or xpc_object_t) are transport-specific - if the goal is to maintain a stable protocol that can be evolved independently of LSP (different methods or semantics) then this isn't really a transport, it's a new protocol. I think the best option is probably to skip `ClangdLSPServer` and have the xpc service wrap `ClangdServer` instead, with a separate main entrypoint. The litmus test here: as people make changes to clangd that affect the LSP surface, do you want the XPC service's interface to change too? This is the hardest model to support as the C++ API (ClangdServer) is not a stable one, but it's what we do for the other non-LSP clients. Does one of these seem close to what you want? Any thoughts on the conclusions? Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D48559 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D48559: [clangd] refactoring for XPC transport layer [NFCI]
jkorous added a comment. Hi Sam, thanks for your feedback! In general I agree that explicitly factoring out the transport layer and improving layering would be a good thing. Unfortunately it's highly probable that we'd like to drop JSON completely from XPC dispatch (XPC -> JSON -> ProtocolCallbacks -> JSON -> XPC) in not too distant future. (Please don't take this as a promise as our priorities might change but it's probable enough that I don't recommend to base any common abstraction on JSON.) I originally tried to create similar abstraction but ultimately gave up as it was way too complex. Not saying it's impossible or that I would not like to though! I basically see these different parts of the problem: 1. dispatch implementation I think that different dispatch implementations might be better kept separate without any common abstraction as it's not a lot of code and not much of opportunities for shared implementation - DispatcherCommon.h/cpp effectively vanishes in case we drop JSON from XPC dispatch. 2. LSP request deserialization Deserialization of LSP requests for different transport layers is easy - it's a single line of code (currently lambda wrapper in HandlerRegisterer) so it would be easy to parametrize this. 3. LSP response serialization This is where I failed. Basically I haven't found any nice way how to make it a part of transport layer interface. I could imagine having a set of response classes in Protocol.h and toXpc functions but still not sure what kind of interface to have for these in transport layer. Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D48559 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D48559: [clangd] refactoring for XPC transport layer [NFCI]
sammccall added a comment. Hi Jan, Thanks for putting this together, and apologies - this is one of the places where we don't have nice abstractions/layering, so adding XPC was harder than it should be. As I mentioned on the other review, I think maybe this patch isn't invasive enough, we could do a more thorough job of making the json transport a standalone, first-class thing. This makes it easier to swap out for XPC but also would improve the layering in the core clangd classes. I put together a sketch of a `Transport` interface in https://reviews.llvm.org/D49389 (that patch is extremely unfinished and won't compile, but the idea might be interesting). The idea is that we should be able to implement that class for XPC and then it should just drop-in as a replacement for the JSON one. I've indicated there where XPC and JSON implementation can share code (assuming you wouldn't rather "tighten up" the protocol and eliminate some JSON-RPC noise). If you like this direction I'm happy for you to pick the bits you like, or I can finish it as a refactoring and we can make sure it works for XPC. If not, that's fine too - happy to look at other ways to reduce duplication between them. Comment at: DispatcherCommon.h:38 + // Return a context that's aware of the enclosing request, identified by Span. + static Context stash(const trace::Span ); + I think I wrote this bit... it's a hack that I'm not sure deserves to be visible in a header (it was bad enough in JSONRPCDispatcher.cpp!) Rather than exposing it so we can use it twice, I'd hope we can manage to keep it hidden so that JSON and XPC can share it. Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D48559 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D48559: [clangd] refactoring for XPC transport layer [NFCI]
jkorous added a comment. Ping. Added reviewers suggestion. Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D48559 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D48559: [clangd] refactoring for XPC transport layer [NFCI]
jkorous added a comment. Followed by these two patches: [clangd] JSON <-> XPC conversions https://reviews.llvm.org/D48560 [clangd] XPC transport layer https://reviews.llvm.org/D48562 Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D48559 ___ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[PATCH] D48559: [clangd] refactoring for XPC transport layer [NFCI]
jkorous created this revision. jkorous added a project: clang-tools-extra. Herald added subscribers: cfe-commits, MaskRay, ioeric, ilya-biryukov, mgorny. Hi all, We finally finished a self-contained first version of our implementation of alternative transport layer for macOS based on XPC. To enable this I did couple of changes to current clangd design. Generally I aimed for small amount of disruption and tried to keep it simple. The notable changes are: - enabled registration of request handlers to different dispatchers by templatizing registerCallbackHandlers() - factored out parts of JsonDispatcher that could be reused (created DispatcherCommon.h/cpp) - created abstraction over JsonOutput (class LSPOutput) - removed of ClangdLSPServer::run() method so server can be run with different dispatcher - published IsDone and ShutdownRequestReceived through methods in ClangdLSPServer class interface to support new dispatcher We are also putting up the transport layer implementation itself for a review soon so it will be more obvious where are we going to and what motivated some of these changes. Big thanks in advance to all reviewers! Jan Repository: rCTE Clang Tools Extra https://reviews.llvm.org/D48559 Files: CMakeLists.txt ClangdLSPServer.cpp ClangdLSPServer.h DispatcherCommon.cpp DispatcherCommon.h JSONRPCDispatcher.cpp JSONRPCDispatcher.h LSPOutput.h ProtocolHandlers.cpp ProtocolHandlers.h tool/ClangdMain.cpp Index: tool/ClangdMain.cpp === --- tool/ClangdMain.cpp +++ tool/ClangdMain.cpp @@ -19,7 +19,6 @@ #include "llvm/Support/Signals.h" #include "llvm/Support/raw_ostream.h" #include -#include #include #include #include @@ -207,12 +206,6 @@ if (Tracer) TracingSession.emplace(*Tracer); - JSONOutput Out(llvm::outs(), llvm::errs(), - InputMirrorStream ? InputMirrorStream.getPointer() : nullptr, - PrettyPrint); - - clangd::LoggingSession LoggingSession(Out); - // If --compile-commands-dir arg was invoked, check value and override default // path. llvm::Optional CompileCommandsDirPath; @@ -252,11 +245,27 @@ CCOpts.Limit = LimitResults; CCOpts.BundleOverloads = CompletionStyle != Detailed; - // Initialize and run ClangdLSPServer. - ClangdLSPServer LSPServer(Out, CCOpts, CompileCommandsDirPath, Opts); - constexpr int NoShutdownRequestErrorCode = 1; + ClangdLSPServer LSPServer(CCOpts, CompileCommandsDirPath, Opts); + llvm::set_thread_name("clangd.main"); // Change stdin to binary to not lose \r\n on windows. llvm::sys::ChangeStdinToBinary(); - return LSPServer.run(stdin, InputStyle) ? 0 : NoShutdownRequestErrorCode; + { +JSONRPCDispatcher Dispatcher([](const json::Expr ) { + replyError(ErrorCode::MethodNotFound, "method not found"); +}); +registerCallbackHandlers(Dispatcher, /*Callbacks=*/LSPServer); + + +JSONOutput Out(llvm::outs(), llvm::errs(), + InputMirrorStream ? InputMirrorStream.getPointer() : nullptr, + PrettyPrint); + +clangd::LoggingSession LoggingSession(Out); + +runJSONRPCServerLoop(stdin, Out, InputStyle, Dispatcher, LSPServer.getIsDone()); + } + + constexpr int NoShutdownRequestErrorCode = 1; + return LSPServer.getShutdownRequestReceived() ? 0 : NoShutdownRequestErrorCode; } Index: ProtocolHandlers.h === --- ProtocolHandlers.h +++ ProtocolHandlers.h @@ -56,8 +56,63 @@ virtual void onChangeConfiguration(DidChangeConfigurationParams ) = 0; }; -void registerCallbackHandlers(JSONRPCDispatcher , - ProtocolCallbacks ); +// Helper for attaching ProtocolCallbacks methods to a JSONRPCDispatcher. +// Invoke like: Registerer("foo", ::onFoo) +// onFoo should be: void onFoo(Ctx , FooParams ) +// FooParams should have a fromJSON function. +template +struct HandlerRegisterer { + template + void operator()(StringRef Method, void (ProtocolCallbacks::*Handler)(Param)) { +// Capture pointers by value, as the lambda will outlive this object. +auto *Callbacks = this->Callbacks; +Dispatcher.registerHandler(Method, [=](const json::Expr ) { + typename std::remove_reference::type P; + if (fromJSON(RawParams, P)) { +(Callbacks->*Handler)(P); + } else { +log("Failed to decode " + Method + " request."); + } +}); + } + + DispatcherType + ProtocolCallbacks *Callbacks; +}; + +template +void registerCallbackHandlers(DispatcherType , + ProtocolCallbacks ) { + HandlerRegisterer Register{Dispatcher, }; + + Register("initialize", ::onInitialize); + Register("shutdown", ::onShutdown); + Register("exit", ::onExit); + Register("textDocument/didOpen", ::onDocumentDidOpen); + Register("textDocument/didClose", ::onDocumentDidClose); + Register("textDocument/didChange", ::onDocumentDidChange); +