use std::path::PathBuf;
use clap::{Args, Subcommand};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::UnixStream;
use lip::query_graph::{ClientMessage, ServerMessage};
use crate::output;
#[derive(Args)]
pub struct QueryArgs {
#[arg(long, default_value = "/tmp/lip-daemon.sock")]
pub socket: PathBuf,
#[command(subcommand)]
pub kind: QueryKind,
}
#[derive(Subcommand)]
pub enum QueryKind {
Definition { uri: String, line: u32, col: u32 },
References {
symbol_uri: String,
#[arg(long, default_value_t = 50)]
limit: usize,
},
Hover { uri: String, line: u32, col: u32 },
BlastRadius { symbol_uri: String },
Symbols {
query: String,
#[arg(long, default_value_t = 100)]
limit: usize,
},
DeadSymbols {
#[arg(long, default_value_t = 200)]
limit: usize,
},
Similar {
query: String,
#[arg(long, default_value_t = 20)]
limit: usize,
},
StaleFiles {
input: Option<PathBuf>,
},
Batch {
input: Option<PathBuf>,
},
}
pub async fn run(args: QueryArgs) -> anyhow::Result<()> {
if let QueryKind::StaleFiles { ref input } = args.kind {
let raw = match input {
Some(path) => tokio::fs::read(path).await?,
None => {
use tokio::io::AsyncReadExt as _;
let mut buf = Vec::new();
tokio::io::stdin().read_to_end(&mut buf).await?;
buf
}
};
let files: Vec<(String, String)> = serde_json::from_slice(&raw).map_err(|e| {
anyhow::anyhow!("input must be a JSON array of [uri, sha256] pairs: {e}")
})?;
let msg = ClientMessage::QueryStaleFiles { files };
let resp = send_recv(&args.socket, msg).await?;
output::print_json(&resp)?;
return Ok(());
}
if let QueryKind::Batch { ref input } = args.kind {
let raw = match input {
Some(path) => tokio::fs::read(path).await?,
None => {
use tokio::io::AsyncReadExt as _;
let mut buf = Vec::new();
tokio::io::stdin().read_to_end(&mut buf).await?;
buf
}
};
let queries: Vec<ClientMessage> = serde_json::from_slice(&raw).map_err(|e| {
anyhow::anyhow!("batch input is not a valid JSON array of queries: {e}")
})?;
let msg = ClientMessage::BatchQuery { queries };
let resp = send_recv(&args.socket, msg).await?;
output::print_json(&resp)?;
return Ok(());
}
let msg = match args.kind {
QueryKind::Definition { uri, line, col } => {
ClientMessage::QueryDefinition { uri, line, col }
}
QueryKind::References { symbol_uri, limit } => ClientMessage::QueryReferences {
symbol_uri,
limit: Some(limit),
},
QueryKind::Hover { uri, line, col } => ClientMessage::QueryHover { uri, line, col },
QueryKind::BlastRadius { symbol_uri } => ClientMessage::QueryBlastRadius { symbol_uri },
QueryKind::Symbols { query, limit } => ClientMessage::QueryWorkspaceSymbols {
query,
limit: Some(limit),
},
QueryKind::DeadSymbols { limit } => ClientMessage::QueryDeadSymbols { limit: Some(limit) },
QueryKind::Similar { query, limit } => ClientMessage::SimilarSymbols { query, limit },
QueryKind::StaleFiles { .. } => unreachable!("handled above"),
QueryKind::Batch { .. } => unreachable!("handled above"),
};
let resp = send_recv(&args.socket, msg).await?;
output::print_json(&resp)?;
Ok(())
}
async fn send_recv(socket: &PathBuf, msg: ClientMessage) -> anyhow::Result<ServerMessage> {
let mut stream = UnixStream::connect(socket)
.await
.map_err(|e| anyhow::anyhow!("cannot connect to daemon at {}: {e}", socket.display()))?;
let body = serde_json::to_vec(&msg)?;
let len = body.len() as u32;
stream.write_all(&len.to_be_bytes()).await?;
stream.write_all(&body).await?;
let mut len_buf = [0u8; 4];
stream.read_exact(&mut len_buf).await?;
let resp_len = u32::from_be_bytes(len_buf) as usize;
let mut resp_bytes = vec![0u8; resp_len];
stream.read_exact(&mut resp_bytes).await?;
Ok(serde_json::from_slice(&resp_bytes)?)
}