#pragma once #include "Config.h" #include "Convert.h" #include "Indexer.h" #include "Plugin.h" #include "Async/Async.h" #include "Compiler/Command.h" #include "Compiler/Diagnostic.h" #include "Compiler/Preamble.h" #include "Feature/DocumentLink.h" #include "Protocol/Protocol.h" #include namespace clice { struct OpenFile { /// The file version, every edition will increase it. std::uint32_t version = 0; /// The file content. std::string content; /// We build PCH for every opened file. std::optional pch; async::Task pch_build_task; async::Event pch_built_event; std::vector pch_includes; /// For each opened file, we would like to build an AST for it. std::shared_ptr ast; async::Task<> ast_build_task; async::Lock ast_built_lock; /// For header with context, it may have multiple ASTs, use /// an chain to store them. std::unique_ptr next; }; /// A manager for all OpenFile with LRU cache. class ActiveFileManager { public: /// Use shared_ptr to manage the lifetime of OpenFile object in async function. using ActiveFile = std::shared_ptr; /// A double-linked list to store all opened files. While the `first` field of pair (each node /// of list) refers to a key in `index`, the `second` field refers to the OpenFile object. /// In another word, the `index` holds the ownership of path and the `items` holds the /// ownership of OpenFile object. using ListContainer = std::list>; struct ActiveFileIterator : public ListContainer::const_iterator {}; constexpr static size_t DefaultMaxActiveFileNum = 8; constexpr static size_t UnlimitedActiveFileNum = 512; public: /// Create an ActiveFileManager with a default size. ActiveFileManager() : capability(DefaultMaxActiveFileNum) {} ActiveFileManager(const ActiveFileManager&) = delete; ActiveFileManager& operator=(const ActiveFileManager&) = delete; /// Set the maximum active file count and it will be clamped to [1, UnlimitedActiveFileNum]. void set_capability(size_t size) { // Use static_cast to make MSVC happy. capability = std::clamp(size, static_cast(1), UnlimitedActiveFileNum); } /// Get the maximum size of the cache. size_t max_size() const { return capability; } /// Get the current size of the cache. size_t size() const { return index.size(); } /// Try get OpenFile from manager, default construct one if not exists. [[nodiscard]] ActiveFile& get_or_add(llvm::StringRef path); /// Add a OpenFile to the manager. ActiveFile& add(llvm::StringRef path, OpenFile file); [[nodiscard]] bool contains(llvm::StringRef path) const { return index.contains(path); } ActiveFileIterator begin() const { return ActiveFileIterator(items.begin()); } ActiveFileIterator end() const { return ActiveFileIterator(items.end()); } private: ActiveFile& lru_put_impl(llvm::StringRef path, OpenFile file); private: /// The maximum size of the cache. size_t capability; /// The first element is the most recently used, and the last /// element is the least recently used. /// When a file is accessed, it will be moved to the front of the list. /// When a new file is added, if the size exceeds the maximum size, /// the last element will be removed. ListContainer items; /// A map from path to the iterator of the list. llvm::StringMap index; }; class Server { public: Server(); using Self = Server; using Callback = async::Task (*)(Server&, json::Value); template void register_callback(llvm::StringRef name) { using MF = decltype(method); static_assert(std::is_member_function_pointer_v, ""); using F = member_type_t; using Ret = function_return_t; using Params = std::tuple_element_t<0, function_args_t>; Callback callback = [](Server& server, json::Value value) -> async::Task { if constexpr(std::is_same_v>) { co_await (server.*method)(json::deserialize(value)); co_return json::Value(nullptr); } else { co_return co_await (server.*method)(json::deserialize(value)); } }; callbacks.try_emplace(name, callback); } async::Task<> on_receive(json::Value value); private: /// Send a request to the client. async::Task<> request(llvm::StringRef method, json::Value params); /// Send a notification to the client. async::Task<> notify(llvm::StringRef method, json::Value params); /// Send a response to the client. async::Task<> response(json::Value id, json::Value result); async::Task<> response(json::Value id, proto::ErrorCodes code, llvm::StringRef message = ""); /// Send an register capability to the client. async::Task<> registerCapacity(llvm::StringRef id, llvm::StringRef method, json::Value registerOptions); private: async::Task on_initialize(proto::InitializeParams params); async::Task<> on_initialized(proto::InitializedParams); async::Task on_shutdown(proto::ShutdownParams params); async::Task<> on_exit(proto::ExitParams params); private: /// Load the cache info from disk. void load_cache_info(); /// Save the cache info to disk. void save_cache_info(); async::Task build_pch(std::string file, std::string preamble); async::Task<> build_ast(std::string file, std::string content); async::Task> add_document(std::string path, std::string content); private: async::Task<> on_did_open(proto::DidOpenTextDocumentParams params); async::Task<> on_did_change(proto::DidChangeTextDocumentParams params); async::Task<> on_did_save(proto::DidSaveTextDocumentParams params); async::Task<> on_did_close(proto::DidCloseTextDocumentParams params); private: using Result = async::Task; auto on_completion(proto::CompletionParams params) -> Result; auto on_hover(proto::HoverParams params) -> Result; auto on_signature_help(proto::SignatureHelpParams params) -> Result; auto on_go_to_declaration(proto::DeclarationParams params) -> Result; auto on_go_to_definition(proto::DefinitionParams params) -> Result; auto on_find_references(proto::ReferenceParams params) -> Result; auto on_document_symbol(proto::DocumentSymbolParams params) -> Result; auto on_document_link(proto::DocumentLinkParams params) -> Result; auto on_document_format(proto::DocumentFormattingParams params) -> Result; auto on_document_range_format(proto::DocumentRangeFormattingParams params) -> Result; auto on_folding_range(proto::FoldingRangeParams params) -> Result; auto on_semantic_token(proto::SemanticTokensParams params) -> Result; auto on_inlay_hint(proto::InlayHintParams params) -> Result; private: /// The current request id. std::uint32_t server_request_id = 0; std::uint32_t client_request_id = 0; /// All registered LSP callbacks. llvm::StringMap callbacks; PositionEncodingKind kind = PositionEncodingKind::UTF16; std::string workspace; /// The compilation database. CompilationDatabase database; /// All opening files. ActiveFileManager opening_files; PathMapping mapping; config::Config config; Indexer indexer; private: friend struct ServerPluginBuilder; using lifecycle_hook_t = llvm::unique_function()>; using command_handler_t = llvm::unique_function( llvm::ArrayRef arguments)>; std::vector initialize_hooks; std::vector initialized_hooks; std::vector shutdown_hooks; std::vector exit_hooks; std::vector did_change_configuration_hooks; llvm::StringMap command_handlers; std::vector plugins; }; } // namespace clice