use crate::{arguments::CommunicationsChannel, capabilities};
use lsp_server::{Connection, IoThreads, Message, Response};
use lsp_types::{InitializeResult, ServerInfo};
use std::collections::HashMap;
mod handlers;
mod utils;
#[derive(Debug, Clone)]
pub struct Document {
pub version: i32,
pub text: String,
}
pub type Documents = HashMap<String, Document>;
pub struct Server {
pub connection: Connection,
pub io_threads: IoThreads,
pub documents: Documents,
}
impl Server {
pub fn new(channel: Option<CommunicationsChannel>) -> Self {
let (connection, io_threads) = Server::resolve_communications_channel(channel);
Self {
connection,
io_threads,
documents: HashMap::new(),
}
}
pub fn run(self) -> anyhow::Result<()> {
let Self {
connection,
io_threads,
mut documents,
} = self;
let init_value = serde_json::to_value(InitializeResult {
capabilities: 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_params = match connection.initialize(init_value) {
Ok(params) => {
tracing::info!("LSP initialize handshake completed");
params
}
Err(err) => {
if err.channel_is_disconnected() {
tracing::warn!("client disconnected during initialize");
io_threads.join()?;
}
return Err(err.into());
}
};
for msg in &connection.receiver {
match msg {
Message::Request(req) => {
tracing::debug!(method = %req.method, id = ?req.id, "received LSP request");
if connection.handle_shutdown(&req)? {
tracing::info!("received LSP shutdown request");
break;
}
match req.method.as_str() {
"textDocument/documentSymbol" => {
handlers::document_symbol::handle(&connection, &req, &documents)?;
}
"textDocument/formatting" => {
handlers::formatting::handle(&connection, &req, &documents)?;
}
"textDocument/foldingRange" => {
handlers::folding_range::handle(&connection, &req, &documents)?;
}
"textDocument/selectionRange" => {
handlers::selection_range::handle(&connection, &req, &documents)?;
}
"textDocument/hover" => {
handlers::hover::handle(&connection, &req, &documents)?;
}
"textDocument/completion" => {
handlers::completion::handle(&connection, &req, &documents)?;
}
"textDocument/definition" => {
handlers::definition::handle(&connection, &req, &documents)?;
}
"textDocument/references" => {
handlers::references::handle(&connection, &req, &documents)?;
}
"textDocument/rename" => {
handlers::rename::handle(&connection, &req, &documents)?;
}
"textDocument/prepareRename" => {
handlers::prepare_rename::handle(&connection, &req, &documents)?;
}
"workspace/symbol" => {
handlers::workspace_symbol::handle(&connection, &req, &documents)?;
}
_ => {
tracing::warn!(method = %req.method, "unable to handle LSP request");
let response = Response {
id: req.id.clone(),
result: None,
error: None,
};
connection.sender.send(Message::Response(response))?;
}
}
}
Message::Notification(note) => match note.method.as_str() {
"textDocument/didOpen" => {
tracing::debug!(method = %note.method, "received LSP notification");
handlers::did_open::handle(&connection, ¬e, &mut documents)?;
}
"textDocument/didChange" => {
tracing::debug!(method = %note.method, "received LSP notification");
handlers::did_change::handle(&connection, ¬e, &mut documents)?;
}
"textDocument/didClose" => {
tracing::debug!(method = %note.method, "received LSP notification");
handlers::did_close::handle(&connection, ¬e, &mut documents)?;
}
_ => {
tracing::debug!(method = %note.method, "ignoring LSP notification");
}
},
Message::Response(resp) => {
tracing::debug!(response = ?resp, "received unexpected LSP response");
}
}
}
tracing::info!("joining LSP IO threads");
io_threads.join()?;
tracing::info!("LSP server run loop exited");
Ok(())
}
fn resolve_communications_channel(
channel: Option<CommunicationsChannel>,
) -> (Connection, IoThreads) {
if let Some(chan) = channel {
match chan {
CommunicationsChannel::Stdio => {
tracing::info!("using stdio communication channel");
Connection::stdio()
}
_ => {
tracing::error!("server does not support communication channel: {}", chan);
std::process::exit(0);
}
}
} else {
tracing::error!("no communication channel provided");
std::process::exit(0)
}
}
}