use lsp_types::request::Request;
use std::marker::PhantomData;
use tokio::sync::mpsc;
use tracing::{debug, info};
use crate::clangd::config::ClangdConfig;
use crate::clangd::error::ClangdSessionError;
use crate::clangd::index::{IndexProgressMonitor, ProgressEvent};
use crate::clangd::log_monitor::LogMonitor;
use crate::clangd::session::ClangdSession;
use crate::io::{ChildProcessManager, ProcessManager, StderrMonitor, StdioTransport};
use crate::lsp::{LspClient, traits::LspClientTrait};
pub struct HasConfig;
pub struct NoConfig;
pub struct HasProcessManager;
pub struct NoProcessManager;
pub struct HasLspClient;
pub struct NoLspClient;
pub struct ClangdSessionBuilder<ConfigState = NoConfig, P = NoProcessManager, C = NoLspClient> {
config: Option<ClangdConfig>,
process_manager: Option<P>,
lsp_client: Option<C>,
progress_sender: Option<mpsc::Sender<ProgressEvent>>,
_phantom: PhantomData<(ConfigState, P, C)>,
}
impl ClangdSessionBuilder<NoConfig, NoProcessManager, NoLspClient> {
pub fn new() -> Self {
Self {
config: None,
process_manager: None,
lsp_client: None,
progress_sender: None,
_phantom: PhantomData,
}
}
}
impl<P, C> ClangdSessionBuilder<NoConfig, P, C> {
pub fn with_config(self, config: ClangdConfig) -> ClangdSessionBuilder<HasConfig, P, C> {
ClangdSessionBuilder {
config: Some(config),
process_manager: self.process_manager,
lsp_client: self.lsp_client,
progress_sender: self.progress_sender,
_phantom: PhantomData,
}
}
}
impl<ConfigState, C> ClangdSessionBuilder<ConfigState, NoProcessManager, C> {
pub fn with_process_manager<P>(
self,
process_manager: P,
) -> ClangdSessionBuilder<ConfigState, P, C>
where
P: ProcessManager + 'static,
{
ClangdSessionBuilder {
config: self.config,
process_manager: Some(process_manager),
lsp_client: self.lsp_client,
progress_sender: self.progress_sender,
_phantom: PhantomData,
}
}
}
impl<ConfigState, P> ClangdSessionBuilder<ConfigState, P, NoLspClient> {
pub fn with_lsp_client<C>(self, lsp_client: C) -> ClangdSessionBuilder<ConfigState, P, C>
where
C: LspClientTrait + 'static,
{
ClangdSessionBuilder {
config: self.config,
process_manager: self.process_manager,
lsp_client: Some(lsp_client),
progress_sender: self.progress_sender,
_phantom: PhantomData,
}
}
}
impl<ConfigState, P, C> ClangdSessionBuilder<ConfigState, P, C> {
pub fn with_progress_sender(mut self, sender: mpsc::Sender<ProgressEvent>) -> Self {
self.progress_sender = Some(sender);
self
}
}
impl ClangdSessionBuilder<HasConfig, NoProcessManager, NoLspClient> {
pub async fn build(
self,
) -> Result<ClangdSession<ChildProcessManager, LspClient<StdioTransport>>, ClangdSessionError>
{
info!("Starting clangd session");
let config = self.config.unwrap();
let log_monitor = if let Some(ref progress_sender) = self.progress_sender {
LogMonitor::with_sender(progress_sender.clone())
} else {
LogMonitor::new()
};
let mut process_manager = Self::create_process_manager_without_start(&config).await?;
let stderr_processor = log_monitor.create_stderr_processor();
process_manager.on_stderr_line(move |line: String| {
stderr_processor(line);
});
process_manager.start().await?;
let mut lsp_client =
Self::create_lsp_client(&config, process_manager.create_stdio_transport()?).await?;
let index_progress_monitor =
Self::setup_monitoring(&mut lsp_client, self.progress_sender.clone()).await;
Self::finalize_session(
config,
process_manager,
lsp_client,
index_progress_monitor,
log_monitor,
)
}
}
impl<P, C> ClangdSessionBuilder<HasConfig, P, C>
where
P: ProcessManager + StderrMonitor + 'static,
C: LspClientTrait + 'static,
{
pub async fn build(self) -> Result<ClangdSession<P, C>, ClangdSessionError> {
let config = self.config.unwrap(); let process_manager = self.process_manager.unwrap(); let lsp_client = self.lsp_client.unwrap(); let index_progress_monitor = if let Some(ref progress_sender) = self.progress_sender {
IndexProgressMonitor::with_sender(progress_sender.clone())
} else {
IndexProgressMonitor::new()
};
let log_monitor = if let Some(progress_sender) = self.progress_sender {
LogMonitor::with_sender(progress_sender)
} else {
LogMonitor::new()
};
let session = ClangdSession::with_dependencies(
config,
process_manager,
lsp_client,
index_progress_monitor,
log_monitor,
);
Ok(session)
}
}
impl ClangdSessionBuilder<HasConfig, NoProcessManager, NoLspClient> {
async fn create_process_manager(
config: &ClangdConfig,
) -> Result<ChildProcessManager, ClangdSessionError> {
debug!("Working directory: {:?}", config.working_directory);
debug!("Build directory: {:?}", config.build_directory);
debug!("Clangd path: {}", config.clangd_path);
let args = config.get_clangd_args();
let mut process_manager = ChildProcessManager::new(
config.clangd_path.clone(),
args,
Some(config.working_directory.clone()),
);
debug!("Starting clangd process");
process_manager.start().await?;
Ok(process_manager)
}
async fn create_process_manager_without_start(
config: &ClangdConfig,
) -> Result<ChildProcessManager, ClangdSessionError> {
debug!("Working directory: {:?}", config.working_directory);
debug!("Build directory: {:?}", config.build_directory);
debug!("Clangd path: {}", config.clangd_path);
let args = config.get_clangd_args();
let process_manager = ChildProcessManager::new(
config.clangd_path.clone(),
args,
Some(config.working_directory.clone()),
);
Ok(process_manager)
}
async fn create_lsp_client(
config: &ClangdConfig,
transport: StdioTransport,
) -> Result<LspClient<StdioTransport>, ClangdSessionError> {
debug!("Creating LSP client");
let mut lsp_client = LspClient::new(transport);
debug!("Initializing LSP connection");
let root_uri = config.get_root_uri();
let init_result = tokio::time::timeout(
config.lsp_config.initialization_timeout,
lsp_client.initialize(root_uri),
)
.await
.map_err(|_| {
ClangdSessionError::operation_timeout(
"LSP initialization",
config.lsp_config.initialization_timeout,
)
})??;
debug!(
"LSP initialization completed: {:?}",
init_result.capabilities
);
Ok(lsp_client)
}
async fn setup_monitoring(
lsp_client: &mut LspClient<StdioTransport>,
progress_sender: Option<mpsc::Sender<ProgressEvent>>,
) -> IndexProgressMonitor {
debug!("Creating and wiring IndexProgressMonitor");
let index_progress_monitor = if let Some(sender) = progress_sender {
IndexProgressMonitor::with_sender(sender)
} else {
IndexProgressMonitor::new()
};
let notification_handler = index_progress_monitor.create_handler();
lsp_client
.register_notification_handler(notification_handler)
.await;
lsp_client
.register_request_handler(Self::create_request_handler())
.await;
debug!("IndexProgressMonitor and request handler wired successfully");
index_progress_monitor
}
fn create_request_handler()
-> impl Fn(crate::lsp::protocol::JsonRpcRequest) -> crate::lsp::protocol::JsonRpcResponse
+ Send
+ Sync
+ 'static {
move |request| {
use crate::lsp::jsonrpc_utils;
match request.method.as_str() {
lsp_types::request::WorkDoneProgressCreate::METHOD => {
debug!(
"Accepting {} request: {:?}",
lsp_types::request::WorkDoneProgressCreate::METHOD,
request.id
);
jsonrpc_utils::null_success_response(request.id)
}
_ => jsonrpc_utils::method_not_found_response(request.id, &request.method),
}
}
}
fn finalize_session(
config: ClangdConfig,
process_manager: ChildProcessManager,
lsp_client: LspClient<StdioTransport>,
index_progress_monitor: IndexProgressMonitor,
log_monitor: LogMonitor,
) -> Result<ClangdSession<ChildProcessManager, LspClient<StdioTransport>>, ClangdSessionError>
{
info!("Clangd session started successfully");
let session = ClangdSession::with_dependencies(
config,
process_manager,
lsp_client,
index_progress_monitor,
log_monitor,
);
Ok(session)
}
}