use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc;
use tracing::{debug, info, instrument, warn};
use crate::clangd::config::DEFAULT_WORKSPACE_SYMBOL_LIMIT;
use crate::clangd::file_manager::ClangdFileManager;
use crate::clangd::session::ClangdSessionTrait;
use crate::clangd::version::ClangdVersion;
use crate::clangd::{ClangdConfigBuilder, ClangdSession, ClangdSessionBuilder};
use crate::io::file_system::RealFileSystem;
#[cfg(all(test, feature = "clangd-integration-tests"))]
use crate::project::index::ComponentIndexState;
use crate::project::index::reader::{IndexReader, IndexReaderTrait};
use crate::project::index::storage::IndexStorage;
use crate::project::index::storage::filesystem::FilesystemIndexStorage;
use crate::project::index::{
ClangdIndexTrigger, ComponentIndexMonitor, ComponentIndexingState, IndexStatusView,
};
use crate::project::{CompilationDatabase, ProjectComponent, ProjectError};
const PROGRESS_CHANNEL_BUFFER_SIZE: usize = 10_000;
pub struct ComponentSession {
build_dir: PathBuf,
clangd_session: Arc<tokio::sync::Mutex<ClangdSession>>,
file_manager: Arc<tokio::sync::Mutex<ClangdFileManager>>,
index_monitor: Arc<ComponentIndexMonitor>,
#[allow(dead_code)]
component: ProjectComponent,
}
impl ComponentSession {
#[instrument(name = "component_session_new", skip(component, clangd_version))]
pub async fn new(
component: ProjectComponent,
clangd_path: &str,
clangd_version: &ClangdVersion,
project_root: PathBuf,
) -> Result<Self, ProjectError> {
info!(
"Creating ComponentSession for build dir: {}",
component.build_dir_path.display()
);
let compilation_database = CompilationDatabase::new(
component.compilation_database_path.clone(),
)
.map_err(|_e| ProjectError::CompilationDatabaseNotFound {
path: component
.compilation_database_path
.to_string_lossy()
.to_string(),
})?;
let compilation_database = Arc::new(compilation_database);
let config = ClangdConfigBuilder::new()
.working_directory(project_root)
.build_directory(component.build_dir_path.clone())
.clangd_path(clangd_path.to_string())
.add_arg(format!(
"--limit-results={}",
DEFAULT_WORKSPACE_SYMBOL_LIMIT
))
.add_arg("--query-driver=**")
.add_arg("--log=verbose")
.build()
.map_err(|e| ProjectError::SessionCreation(format!("Failed to build config: {}", e)))?;
let (progress_tx, mut progress_rx) = mpsc::channel(PROGRESS_CHANNEL_BUFFER_SIZE);
let session = ClangdSessionBuilder::new()
.with_config(config)
.with_progress_sender(progress_tx)
.build()
.await
.map_err(|e| {
ProjectError::SessionCreation(format!("Failed to create session: {}", e))
})?;
let clangd_session = Arc::new(tokio::sync::Mutex::new(session));
let file_manager = Arc::new(tokio::sync::Mutex::new(ClangdFileManager::new()));
let index_monitor = Self::create_index_monitor(
&component,
compilation_database.clone(),
clangd_version,
Arc::clone(&clangd_session),
Arc::clone(&file_manager),
)
.await?;
let monitor_clone = Arc::clone(&index_monitor);
tokio::spawn(async move {
while let Some(event) = progress_rx.recv().await {
monitor_clone.handle_progress_event(event).await;
}
});
debug!(
"ComponentSession created successfully for build dir: {}",
component.build_dir_path.display()
);
Ok(Self {
build_dir: component.build_dir_path.clone(),
clangd_session,
file_manager,
index_monitor,
component,
})
}
async fn create_index_monitor(
component: &ProjectComponent,
compilation_database: Arc<CompilationDatabase>,
clangd_version: &ClangdVersion,
session: Arc<tokio::sync::Mutex<ClangdSession>>,
file_manager: Arc<tokio::sync::Mutex<ClangdFileManager>>,
) -> Result<Arc<ComponentIndexMonitor>, ProjectError> {
let build_dir = &component.build_dir_path;
let index_directory = build_dir.join(".cache/clangd/index");
let expected_version = clangd_version.index_format_version();
let storage: Arc<dyn IndexStorage> = Arc::new(FilesystemIndexStorage::new(
index_directory,
expected_version,
RealFileSystem,
));
let index_reader: Arc<dyn IndexReaderTrait> =
Arc::new(IndexReader::new(storage, clangd_version.clone()));
let index_trigger = Arc::new(ClangdIndexTrigger::new(session, file_manager));
let monitor = ComponentIndexMonitor::new_with_trigger(
build_dir.to_path_buf(),
compilation_database.clone(),
index_reader,
clangd_version,
Some(index_trigger),
)
.await?;
if let Err(e) = monitor
.trigger_initial_indexing(compilation_database.clone())
.await
{
warn!(
"Failed to trigger initial indexing for {}: {}",
build_dir.display(),
e
);
}
let monitor_arc = Arc::new(monitor);
debug!(
"Created ComponentIndexMonitor for build dir: {}",
build_dir.display()
);
Ok(monitor_arc)
}
pub async fn ensure_file_ready(&self, path: &std::path::Path) -> Result<(), ProjectError> {
let mut session = self.clangd_session.lock().await;
let mut file_manager = self.file_manager.lock().await;
file_manager
.ensure_file_ready(path, session.client_mut())
.await
.map_err(|e| ProjectError::SessionCreation(format!("File management failed: {}", e)))
}
pub async fn lsp_session(&self) -> tokio::sync::MutexGuard<'_, ClangdSession> {
self.clangd_session.lock().await
}
pub fn build_dir(&self) -> &PathBuf {
&self.build_dir
}
pub async fn ensure_indexed(&self, timeout: Duration) -> Result<(), ProjectError> {
self.wait_for_indexing_completion(timeout).await
}
#[cfg(all(test, feature = "clangd-integration-tests"))]
pub async fn get_index_state(&self) -> ComponentIndexState {
(*self.index_monitor).get_component_state().await
}
pub async fn wait_for_indexing_completion(
&self,
timeout: Duration,
) -> Result<(), ProjectError> {
info!(
"Waiting for indexing completion for build dir: {} (timeout: {:?})",
self.build_dir.display(),
timeout
);
self.index_monitor.wait_for_completion(timeout).await?;
Ok(())
}
pub async fn get_index_status(&self) -> IndexStatusView {
let (component_state, start_time) = self.index_monitor.get_progress_data().await;
let in_progress = matches!(component_state.state, ComponentIndexingState::InProgress(_));
let progress_percentage =
if let ComponentIndexingState::InProgress(percentage) = component_state.state {
Some(percentage)
} else {
None
};
let state_str = match component_state.state {
ComponentIndexingState::Init => "Init".to_string(),
ComponentIndexingState::InProgress(percent) => format!("InProgress({:.1}%)", percent),
ComponentIndexingState::Partial => "Partial".to_string(),
ComponentIndexingState::Completed => "Completed".to_string(),
};
IndexStatusView::new(
in_progress,
progress_percentage,
component_state.indexed_cdb_files,
component_state.total_cdb_files,
start_time,
state_str,
)
}
}