achitek-ls 0.2.0

achitekfile language server
Documentation
use crate::{
    arguments::CommunicationsChannel,
    lsp,
    server::{ServerState, dispatch},
    workspace::{self, Workspace},
};
use lsp_server::{Connection, Message};
use lsp_types::{InitializeParams, InitializeResult, ServerInfo};
use std::path::PathBuf;

pub fn run(channel: Option<CommunicationsChannel>) -> anyhow::Result<()> {
    let (connection, io_threads) = match channel.unwrap_or_default() {
        CommunicationsChannel::Stdio => {
            tracing::info!("using stdio communication channel");
            Connection::stdio()
        }
        chan => {
            tracing::error!("server does not support communication channel: {}", chan);
            std::process::exit(0);
        }
    };

    let hand_shake = serde_json::to_value(InitializeResult {
        capabilities: lsp::capabilities::make(),
        server_info: Some(ServerInfo {
            name: env!("CARGO_PKG_NAME").to_owned(),
            version: Some(env!("CARGO_PKG_VERSION").to_owned()),
        }),
    })?;

    tracing::info!("waiting for LSP initialize request");

    let (init_id, init_params) = match connection.initialize_start() {
        Ok(parts) => parts,
        Err(err) => {
            if err.channel_is_disconnected() {
                tracing::warn!(
                    "client disconnected during the beginning of initialization ceremony"
                );
                io_threads.join()?;
            }
            return Err(err.into());
        }
    };

    match connection.initialize_finish(init_id, hand_shake) {
        Ok(()) => {
            tracing::info!("LSP initialize handshake completed");
        }
        Err(err) => {
            if err.channel_is_disconnected() {
                tracing::warn!("client disconnected during the end of initialization ceremony");
                io_threads.join()?;
            }
            return Err(err.into());
        }
    }

    let workspace = workspace_root_from_init_params(init_params)
        .and_then(|root| match Workspace::discover(&root) {
            Ok(workspace) => Some(workspace),
            Err(error) => {
                tracing::warn!(
                    root = %root.display(),
                    error = %error,
                    "failed to discover blueprint workspace"
                );
                None
            }
        })
        .unwrap_or_default();

    let mut state = ServerState::with_workspace(workspace);

    for msg in &connection.receiver {
        match msg {
            Message::Request(request) => {
                if connection.handle_shutdown(&request)? {
                    tracing::info!("received LSP shutdown request");
                    break;
                }
                dispatch::handle_request(&connection, &state, request)?;
            }
            Message::Notification(notification) => {
                dispatch::handle_notification(&connection, &mut state, notification)?;
            }
            Message::Response(response) => {
                tracing::debug!(response = ?response, "received unexpected LSP response");
            }
        }
    }

    tracing::info!("joining LSP IO threads");
    io_threads.join()?;
    tracing::info!("LSP server run loop exited");

    Ok(())
}

fn workspace_root_from_init_params(init_params: serde_json::Value) -> Option<PathBuf> {
    let params = serde_json::from_value::<InitializeParams>(init_params)
        .inspect_err(|error| tracing::warn!(%error, "failed to parse initialize params"))
        .ok()?;

    let workspace_folder_root = params.workspace_folders.as_ref().and_then(|folders| {
        folders
            .iter()
            .find_map(|folder| workspace::file_path_from_uri(&folder.uri))
    });

    workspace_folder_root.or_else(|| root_uri_workspace_path(params))
}

#[allow(deprecated)]
fn root_uri_workspace_path(params: InitializeParams) -> Option<PathBuf> {
    params
        .root_uri
        .and_then(|uri| workspace::file_path_from_uri(&uri))
}