use crate::lsp::{read_frame, write_frame};
use futures_util::{SinkExt, StreamExt};
use std::process::Stdio;
use tauri::Emitter;
use tokio::io::BufReader;
use tokio::net::TcpListener;
use tokio::process::Command;
use tokio_tungstenite::tungstenite::Message;
fn adapter_for(kind: &str) -> Option<(String, Vec<String>)> {
match kind {
"python" => Some(("python3".into(), vec!["-m".into(), "debugpy.adapter".into()])),
"python-py" => Some(("python".into(), vec!["-m".into(), "debugpy.adapter".into()])),
"node" => Some(("js-debug-adapter".into(), vec![])),
"lldb" => Some(("codelldb".into(), vec![])),
_ => None,
}
}
#[tauri::command]
pub fn dap_adapters() -> Vec<String> {
["python", "node", "lldb"]
.iter()
.filter(|k| {
adapter_for(k)
.map(|(p, _)| which(&p).is_some())
.unwrap_or(false)
})
.map(|s| s.to_string())
.collect()
}
fn which(program: &str) -> Option<()> {
let path = std::env::var_os("PATH")?;
for dir in std::env::split_paths(&path) {
if dir.join(program).is_file() {
return Some(());
}
}
None
}
#[tauri::command]
pub async fn dap_start(app: tauri::AppHandle, kind: String) -> Result<u16, String> {
let (program, args) = adapter_for(&kind)
.or_else(|| adapter_for("python-py").filter(|_| kind == "python"))
.ok_or_else(|| format!("no debug adapter for {kind}"))?;
let listener = TcpListener::bind("127.0.0.1:0")
.await
.map_err(|e| e.to_string())?;
let port = listener.local_addr().map_err(|e| e.to_string())?.port();
let err_handle = app.clone();
tokio::spawn(async move {
let Ok((stream, _peer)) = listener.accept().await else { return };
let ws = match tokio_tungstenite::accept_async(stream).await {
Ok(ws) => ws,
Err(e) => {
let _ = err_handle.emit("dap:error", format!("ws handshake: {e}"));
return;
}
};
let mut child = match Command::new(&program)
.args(&args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
{
Ok(c) => c,
Err(e) => {
let _ = app.emit("dap:error", format!("{program}: {e}"));
return;
}
};
let mut child_stdin = child.stdin.take().unwrap();
let mut reader = BufReader::new(child.stdout.take().unwrap());
let (mut ws_tx, mut ws_rx) = ws.split();
let to_ws = async move {
while let Ok(Some(body)) = read_frame(&mut reader).await {
if ws_tx.send(Message::Text(body.into())).await.is_err() {
break;
}
}
let _ = ws_tx.close().await;
};
let to_child = async move {
while let Some(Ok(msg)) = ws_rx.next().await {
match msg {
Message::Text(_) | Message::Binary(_) => {
if write_frame(&mut child_stdin, &msg.into_data()).await.is_err() {
break;
}
}
Message::Close(_) => break,
_ => {}
}
}
};
tokio::select! { _ = to_ws => {}, _ = to_child => {} }
let _ = child.kill().await;
});
Ok(port)
}