#include "Support/Logger.h" #include "Server/Server.h" #include "Compiler/Compilation.h" #include "Feature/Diagnostic.h" namespace clice { async::Task Server::add_document(std::string path, std::string content) { auto& openFile = opening_files[path]; openFile.content = content; auto& task = openFile.ast_build_task; /// If there is already an AST build task, cancel it. if(!task.empty()) { task.cancel(); task.dispose(); } /// Create and schedule a new task. task = build_ast(std::move(path), std::move(content)); co_await task; co_return &opening_files[path]; } async::Task<> Server::build_pch(std::string path, std::string content) { auto bound = computePreambleBound(content); auto openFile = &opening_files[path]; bool outdated = true; if(openFile->pch) { /// FIXME: /// outdated = co_await isPCHOutdated(path, llvm::StringRef(content).substr(0, bound)); } /// If not need update, return directly. if(!outdated) { co_return; } /// The actual PCH build task. constexpr static auto PCHBuildTask = [](Server& server, std::string path, std::uint32_t bound, std::string content, std::shared_ptr> diagnostics) -> async::Task { if(!fs::exists(config::cache.dir)) { auto error = fs::create_directories(config::cache.dir); if(error) { log::warn("Fail to create directory for PCH building: {}", config::cache.dir); co_return false; } } /// Everytime we build a new pch, the old diagnostics should be discarded. diagnostics->clear(); CompilationParams params; params.outPath = path::join(config::cache.dir, path::filename(path) + ".pch"); params.arguments = server.database.get_command(path, true, true).arguments; params.diagnostics = diagnostics; params.add_remapped_file(path, content, bound); PCHInfo info; std::string command; for(auto argument: params.arguments) { command += " "; command += argument; } log::info("Start building PCH for {}, command: [{}]", path, command); /// PCH file is written until destructing, Add a single block /// for it. bool cond = co_await async::submit([&] { auto result = compile(params, info); if(!result) { log::warn("Building PCH fails for {}, Because: {}", path, result.error()); for(auto& diagnostic: *diagnostics) { log::warn("{}", diagnostic.message); } return false; } /// TODO: index PCH. return true; }); if(!cond) { co_return false; } auto& openFile = server.opening_files[path]; /// Update the built PCH info. openFile.pch = std::move(info); /// Resume waiters on this event. openFile.pch_built_event.set(); openFile.pch_built_event.clear(); co_return true; }; openFile = &opening_files[path]; /// If there is already an PCH build task, cancel it. auto& task = openFile->pch_build_task; if(!task.empty()) { task.cancel(); task.dispose(); } /// Schedule the new building task. task = PCHBuildTask(*this, path, bound, std::move(content), openFile->diagnostics); if(co_await task) { log::info("Building PCH successfully for {}", path); /// Dispose the task so that it will destroyed when task complete. task.dispose(); } /// TODO: report diagnostics in the preamble. } async::Task<> Server::build_ast(std::string path, std::string content) { auto file = &opening_files[path]; /// Try get the lock, the waiter on the lock will be resumed when /// guard is destroyed. auto guard = co_await file->ast_built_lock.try_lock(); /// PCH is already updated. co_await build_pch(path, content); auto pch = opening_files[path].pch; if(!pch) { log::fatal("Expected PCH built at this point"); } file = &opening_files[path]; CompilationParams params; params.arguments = database.get_command(path, true, true).arguments; params.add_remapped_file(path, content); params.pch = {pch->path, pch->preamble.size()}; file->diagnostics->clear(); params.diagnostics = file->diagnostics; /// Check result auto ast = co_await async::submit([&] { return compile(params); }); if(!ast) { /// FIXME: Fails needs cancel waiting tasks. log::warn("Building AST fails for {}, Beacuse: {}", path, ast.error()); co_return; } /// FIXME: Index the source file. /// co_await indexer.index(*ast); file = &opening_files[path]; /// Update built AST info. file->ast = std::make_shared(std::move(*ast)); /// Dispose the task so that it will destroyed when task complete. file->ast_build_task.dispose(); log::info("Building AST successfully for {}", path); } async::Task<> Server::on_did_open(proto::DidOpenTextDocumentParams params) { auto path = mapping.to_path(params.textDocument.uri); auto file = co_await add_document(path, std::move(params.textDocument.text)); if(file->diagnostics) { auto guard = co_await file->ast_built_lock.try_lock(); file = &opening_files[path]; auto diagnostics = feature::diagnostics(kind, mapping, *file->ast); co_await notify("textDocument/publishDiagnostics", json::Object{ {"uri", mapping.to_uri(path) }, {"diagnostics", std::move(diagnostics)}, }); } co_return; } async::Task<> Server::on_did_change(proto::DidChangeTextDocumentParams params) { auto path = mapping.to_path(params.textDocument.uri); auto file = co_await add_document(path, std::move(params.contentChanges[0].text)); if(file->diagnostics) { auto guard = co_await file->ast_built_lock.try_lock(); file = &opening_files[path]; auto diagnostics = feature::diagnostics(kind, mapping, *file->ast); co_await notify("textDocument/publishDiagnostics", json::Object{ {"uri", mapping.to_uri(path) }, {"diagnostics", std::move(diagnostics)}, }); } co_return; } async::Task<> Server::on_did_save(proto::DidSaveTextDocumentParams params) { auto path = mapping.to_path(params.textDocument.uri); co_return; } async::Task<> Server::on_did_close(proto::DidCloseTextDocumentParams params) { auto path = mapping.to_path(params.textDocument.uri); co_return; } } // namespace clice