Files
clice/src/Server/Scheduler.cpp
2025-06-17 22:07:31 +08:00

189 lines
5.6 KiB
C++

#include "Server/Config.h"
#include "Server/Scheduler.h"
#include "Server/LSPConverter.h"
#include "Support/Logger.h"
#include "Support/FileSystem.h"
#include "Compiler/Command.h"
#include "Compiler/Compilation.h"
namespace clice {
void Scheduler::addDocument(std::string path, std::string content) {
auto& openFile = openFiles[path];
openFile.content = content;
auto& task = openFile.ASTBuild;
/// If there is already an AST build task, cancel it.
if(!task.empty()) {
task.cancel();
task.dispose();
}
/// Create and schedule a new task.
task = buildAST(std::move(path), std::move(content));
task.schedule();
}
llvm::StringRef Scheduler::getDocumentContent(llvm::StringRef path) {
return openFiles[path].content;
}
async::Task<json::Value> Scheduler::semanticToken(std::string path) {
auto openFile = &openFiles[path];
auto guard = co_await openFile->ASTBuiltLock.try_lock();
openFile = &openFiles[path];
auto content = openFile->content;
auto AST = openFile->AST;
auto tokens = co_await async::submit([&] { return feature::semanticTokens(*AST); });
co_return converter.convert(content, tokens);
}
async::Task<json::Value> Scheduler::completion(std::string path, std::uint32_t offset) {
/// Wait for PCH building.
auto openFile = &openFiles[path];
if(!openFile->PCHBuild.empty()) {
co_await openFile->PCHBuiltEvent;
}
openFile = &openFiles[path];
auto& PCH = openFile->PCH;
/// Set compilation params ... .
CompilationParams params;
params.command = database.getCommand(path);
params.add_remapped_file(path, openFile->content);
params.pch = {PCH->path, PCH->preamble.size()};
params.completion = {path, offset};
auto result = co_await async::submit([&] { return feature::codeCompletion(params, {}); });
openFile = &openFiles[path];
co_return converter.convert(openFile->content, result);
}
async::Task<bool> Scheduler::isPCHOutdated(llvm::StringRef path, llvm::StringRef preamble) {
auto openFile = &openFiles[path];
/// If there is not PCH, directly build it.
if(!openFile->PCH) {
co_return true;
}
/// Check command and preamble matchs.
auto command = database.getCommand(path);
if(openFile->PCH->command != command || openFile->PCH->preamble != preamble) {
co_return true;
}
/// TODO: Check mtime.
co_return false;
}
async::Task<> Scheduler::buildPCH(std::string path, std::string content) {
auto bound = computePreambleBound(content);
auto openFile = &openFiles[path];
bool outdated = true;
if(openFile->PCH) {
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 = [](Scheduler& scheduler,
std::string path,
std::uint32_t bound,
std::string content) -> async::Task<> {
CompilationParams params;
params.command = scheduler.database.getCommand(path);
params.outPath = path::join(config::index.dir, path::filename(path) + ".pch");
params.add_remapped_file(path, content, bound);
PCHInfo info;
auto result = co_await async::submit([&] { return compile(params, info); });
if(!result) {
/// FIXME: Fails needs cancel waiting tasks.
log::warn("Building PCH fails for {}", path);
co_return;
}
auto& openFile = scheduler.openFiles[path];
/// Update the built PCH info.
openFile.PCH = std::move(info);
/// Dispose the task so that it will destroyed when task complete.
openFile.PCHBuild.dispose();
/// Resume waiters on this event.
openFile.PCHBuiltEvent.set();
openFile.PCHBuiltEvent.clear();
log::info("Building PCH successfully for {}", path);
};
openFile = &openFiles[path];
/// If there is already an PCH build task, cancel it.
auto& task = openFile->PCHBuild;
if(!task.empty()) {
task.cancel();
task.dispose();
}
/// Schedule the new building task.
task = PCHBuildTask(*this, std::move(path), bound, std::move(content));
task.schedule();
/// Waiting for PCH building.
co_await openFile->PCHBuiltEvent;
}
async::Task<> Scheduler::buildAST(std::string path, std::string content) {
auto file = &openFiles[path];
/// Try get the lock, the waiter on the lock will be resumed when
/// guard is destroyed.
auto guard = co_await file->ASTBuiltLock.try_lock();
/// PCH is already updated.
co_await buildPCH(path, content);
auto PCH = openFiles[path].PCH;
if(!PCH) {
log::fatal("Expected PCH built at this point");
}
CompilationParams params;
params.command = database.getCommand(path);
params.add_remapped_file(path, content);
params.pch = {PCH->path, PCH->preamble.size()};
/// Check result
auto info = co_await async::submit([&] { return compile(params); });
if(!info) {
/// FIXME: Fails needs cancel waiting tasks.
log::warn("Building AST fails for {}", path);
co_return;
}
/// Index the source file.
co_await indexer.index(*info);
file = &openFiles[path];
/// Update built AST info.
file->AST = std::make_shared<CompilationUnit>(std::move(*info));
/// Dispose the task so that it will destroyed when task complete.
file->ASTBuild.dispose();
log::info("Building AST successfully for {}", path);
}
} // namespace clice