rust-analyzer-cli 0.3.1

A library and CLI tool built on top of rust-analyzer for codebase navigation
Documentation
use crate::cargo::run_cargo_check;
use crate::daemon::state::AppState;
use crate::lsp::types::*;
use axum::{
    extract::State,
    http::StatusCode,
    routing::{get, post},
    Json, Router,
};
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use tracing::info;

pub async fn start_daemon_server(workspace_root: PathBuf, port: u16) -> anyhow::Result<()> {
    let state = Arc::new(AppState::new(workspace_root));

    let app = Router::new()
        .route("/status", get(handle_status))
        .route("/refresh", post(handle_refresh))
        .route("/api/symbol", post(handle_symbol))
        .route("/api/outline", post(handle_outline))
        .route("/api/definition", post(handle_definition))
        .route("/api/cursor", post(handle_cursor))
        .route("/api/type-hierarchy", post(handle_type_hierarchy))
        .route("/api/check", post(handle_check))
        .with_state(state);

    let addr = SocketAddr::from(([127, 0, 0, 1], port));
    info!("rust-analyzer-cli daemon listening on http://{}", addr);
    println!("rust-analyzer-cli daemon started on http://{}", addr);

    let listener = tokio::net::TcpListener::bind(addr).await?;
    axum::serve(listener, app).await?;
    Ok(())
}

async fn handle_status(
    State(state): State<Arc<AppState>>,
) -> Result<Json<DaemonStatusResponse>, (StatusCode, String)> {
    let client_opt = state.lsp_client.read().await;
    let (ready, pid) = match client_opt.as_ref() {
        Some(c) => (true, c.process_id),
        None => (false, 0),
    };

    Ok(Json(DaemonStatusResponse {
        ready,
        indexed_at: Some(chrono_now_str()),
        workspace_root: state.workspace_root.to_string_lossy().to_string(),
        process_id: pid,
    }))
}

async fn handle_refresh(
    State(state): State<Arc<AppState>>,
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
    state
        .refresh_lsp()
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
    Ok(Json(serde_json::json!({ "success": true, "message": "LSP session refreshed" })))
}

async fn handle_symbol(
    State(state): State<Arc<AppState>>,
    Json(req): Json<SymbolQueryRequest>,
) -> Result<Json<Vec<SymbolItem>>, (StatusCode, String)> {
    let client = state
        .get_or_start_lsp()
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    let items = client
        .query_symbol(&req.name, &req.kind, req.exact)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Ok(Json(items))
}

async fn handle_outline(
    State(state): State<Arc<AppState>>,
    Json(req): Json<OutlineQueryRequest>,
) -> Result<Json<Vec<OutlineItem>>, (StatusCode, String)> {
    let client = state
        .get_or_start_lsp()
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    let items = client
        .query_outline(&req.file)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Ok(Json(items))
}

async fn handle_definition(
    State(state): State<Arc<AppState>>,
    Json(req): Json<DefinitionQueryRequest>,
) -> Result<Json<Vec<DefinitionItem>>, (StatusCode, String)> {
    let client = state
        .get_or_start_lsp()
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    let items = client
        .query_definition(&req.file, req.line, req.col)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Ok(Json(items))
}

async fn handle_cursor(
    State(state): State<Arc<AppState>>,
    Json(req): Json<CursorQueryRequest>,
) -> Result<Json<Vec<CursorItem>>, (StatusCode, String)> {
    let client = state
        .get_or_start_lsp()
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    let items = client
        .query_cursor(&req.file, req.line, req.col, &req.mode, req.depth)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Ok(Json(items))
}

async fn handle_type_hierarchy(
    State(state): State<Arc<AppState>>,
    Json(req): Json<TypeHierarchyQueryRequest>,
) -> Result<Json<Vec<TypeHierarchyItemResult>>, (StatusCode, String)> {
    let client = state
        .get_or_start_lsp()
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    let items = client
        .query_type_hierarchy(&req.file, req.line, req.col, &req.mode, req.depth)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Ok(Json(items))
}


async fn handle_check(
    State(state): State<Arc<AppState>>,
    Json(req): Json<CheckQueryRequest>,
) -> Result<Json<CheckResponse>, (StatusCode, String)> {
    let res = run_cargo_check(&state.workspace_root, req.target.as_deref())
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Ok(Json(res))
}

fn chrono_now_str() -> String {
    let now = std::time::SystemTime::now();
    format!("{:?}", now)
}