use crate::diagnostics::publish_diagnostics_for_state;
use crate::protocol::RunRobotResult;
use crate::state::ServerState;
use crossbeam_channel::{Receiver, Sender};
use lsp_server::{Message, Notification};
use lsp_types::{
notification::{Notification as _, ShowMessage},
MessageType, ShowMessageParams,
};
use ontocore_catalog::CatalogStats;
use std::path::PathBuf;
use std::thread;
struct IndexJob {
workspace: PathBuf,
reply: Option<Sender<Result<(CatalogStats, u64), String>>>,
}
struct RobotJob {
robot_path: Option<String>,
args: Vec<String>,
reply: Sender<Result<RunRobotResult, String>>,
}
enum WorkerJob {
Index(IndexJob),
Robot(RobotJob),
}
#[derive(Clone)]
pub struct IndexWorker {
job_tx: Sender<WorkerJob>,
}
impl IndexWorker {
pub fn spawn(state: ServerState, lsp_sender: Sender<Message>) -> Self {
let (job_tx, job_rx) = crossbeam_channel::unbounded();
thread::spawn(move || run_worker(state, job_rx, lsp_sender));
Self { job_tx }
}
pub fn enqueue(&self, workspace: PathBuf) {
let _ = self.job_tx.send(WorkerJob::Index(IndexJob { workspace, reply: None }));
}
pub fn enqueue_sync(&self, workspace: PathBuf) -> Result<(CatalogStats, u64), String> {
let (tx, rx) = crossbeam_channel::bounded(1);
self.job_tx
.send(WorkerJob::Index(IndexJob { workspace, reply: Some(tx) }))
.map_err(|e| format!("index worker unavailable: {e}"))?;
rx.recv().map_err(|e| format!("index worker dropped reply: {e}"))?
}
pub fn run_robot_sync(
&self,
robot_path: Option<String>,
args: Vec<String>,
) -> Result<RunRobotResult, String> {
let (tx, rx) = crossbeam_channel::bounded(1);
self.job_tx
.send(WorkerJob::Robot(RobotJob { robot_path, args, reply: tx }))
.map_err(|e| format!("index worker unavailable: {e}"))?;
rx.recv().map_err(|e| format!("index worker dropped reply: {e}"))?
}
}
fn run_worker(state: ServerState, job_rx: Receiver<WorkerJob>, lsp_sender: Sender<Message>) {
while let Ok(first) = job_rx.recv() {
match first {
WorkerJob::Robot(job) => {
let result = run_robot_job(job.robot_path.as_deref(), &job.args);
let _ = job.reply.send(result);
}
WorkerJob::Index(first_index) => {
let mut batch = vec![first_index];
let mut pending_robots = Vec::new();
while let Ok(job) = job_rx.try_recv() {
match job {
WorkerJob::Index(next) => batch.push(next),
WorkerJob::Robot(robot) => pending_robots.push(robot),
}
}
let (silent, sync_jobs): (Vec<_>, Vec<_>) =
batch.into_iter().partition(|j| j.reply.is_none());
if sync_jobs.is_empty() {
if let Some(last) = silent.last() {
run_index_job(&state, &lsp_sender, last.workspace.clone(), &[]);
}
} else {
let mut seen = Vec::new();
for job in &sync_jobs {
let path = &job.workspace;
if seen.iter().any(|p: &PathBuf| p == path) {
continue;
}
seen.push(path.clone());
let replies: Vec<_> = sync_jobs
.iter()
.filter(|j| &j.workspace == path)
.filter_map(|j| j.reply.clone())
.collect();
run_index_job(&state, &lsp_sender, path.clone(), &replies);
}
for job in silent {
if !seen.iter().any(|p| p == &job.workspace) {
let path = job.workspace.clone();
run_index_job(&state, &lsp_sender, path.clone(), &[]);
seen.push(path);
}
}
}
for robot in pending_robots {
let result = run_robot_job(robot.robot_path.as_deref(), &robot.args);
let _ = robot.reply.send(result);
}
}
}
}
}
fn run_index_job(
state: &ServerState,
lsp_sender: &Sender<Message>,
workspace: PathBuf,
replies: &[Sender<Result<(CatalogStats, u64), String>>],
) {
let result = state.index_workspace(workspace);
match &result {
Ok(_) => {
publish_diagnostics_for_state(lsp_sender, state);
}
Err(message) => notify_index_failure(lsp_sender, message),
}
for reply in replies {
let _ = reply.send(result.clone());
}
}
fn run_robot_job(robot_path: Option<&str>, args: &[String]) -> Result<RunRobotResult, String> {
let output = ontocore_robot::run_robot(robot_path, args).map_err(|e| e.to_string())?;
Ok(RunRobotResult { exit_code: output.exit_code, stdout: output.stdout, stderr: output.stderr })
}
fn notify_index_failure(sender: &Sender<Message>, message: &str) {
let params = ShowMessageParams {
typ: MessageType::ERROR,
message: format!("OntoCore reindex failed: {message}"),
};
let notif = Notification {
method: ShowMessage::METHOD.to_string(),
params: serde_json::to_value(params).unwrap_or_default(),
};
let _ = sender.send(Message::Notification(notif));
}