//! Format API — run `el fmt` on source content. use axum::{ extract::State, http::StatusCode, Json, }; use serde::{Deserialize, Serialize}; use crate::AppState; type ApiResult = Result, (StatusCode, Json)>; fn api_err(msg: impl std::fmt::Display) -> (StatusCode, Json) { ( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": msg.to_string() })), ) } #[derive(Debug, Deserialize)] pub struct FormatRequest { pub content: String, /// Optional filename hint for language detection pub path: Option, } #[derive(Debug, Serialize)] pub struct FormatResponse { pub content: String, pub changed: bool, } /// POST /api/format — format content via `el fmt`, falls back to identity. pub async fn format( State(state): State, Json(req): Json, ) -> ApiResult { let project_path = &state.config.project_path; // Write content to a temp file, run `el fmt`, read back let tmp_path = format!("{project_path}/.el-ide-fmt-tmp.el"); tokio::fs::write(&tmp_path, &req.content) .await .map_err(|e| api_err(format!("write tmp: {e}")))?; // Try to find `el` binary let el_bin = if let Ok(path) = which::which("el") { path.to_string_lossy().to_string() } else { // el not installed — return unchanged tokio::fs::remove_file(&tmp_path).await.ok(); return Ok(Json(FormatResponse { content: req.content.clone(), changed: false, })); }; let output = tokio::process::Command::new(&el_bin) .arg("fmt") .arg(&tmp_path) .current_dir(project_path) .output() .await; match output { Ok(out) if out.status.success() => { let formatted = tokio::fs::read_to_string(&tmp_path) .await .unwrap_or_else(|_| req.content.clone()); tokio::fs::remove_file(&tmp_path).await.ok(); let changed = formatted != req.content; Ok(Json(FormatResponse { content: formatted, changed })) } _ => { tokio::fs::remove_file(&tmp_path).await.ok(); Ok(Json(FormatResponse { content: req.content.clone(), changed: false, })) } } }