Files
clice/src/Server/Document.cpp
2025-07-30 23:41:03 +08:00

216 lines
6.8 KiB
C++

#include "Support/Logger.h"
#include "Server/Server.h"
#include "Compiler/Compilation.h"
#include "Feature/Diagnostic.h"
namespace clice {
async::Task<OpenFile*> 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<std::vector<Diagnostic>> diagnostics) -> async::Task<bool> {
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<CompilationUnit>(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