use std::{
collections::HashMap,
error::Error,
path::{Path, PathBuf},
};
use lsp_server::ErrorCode;
use lsp_types::Url;
use tracing::{debug, error, info, warn};
use crate::{
lsp::handlers::{
completions::completion,
definitions::{
document_highlight, document_highlight_capabilities, goto_definition, references,
references_capabilities,
},
ranges::{folding_range, folding_range_capabilities},
rename::{prepare_rename, rename, rename_capabilities},
symbols::{
document_symbol, document_symbol_capabilities, workspace_symbol,
workspace_symbol_capabilities,
},
},
project::{FileHandle, PkgHandle, Project},
util::data::either::Either,
};
use super::{
error::LspErrorString,
handlers::{
completions::completion_capabilities,
definitions::definition_capabilites,
diagnostics::{diagnostic_capabilities, document_diagnostic, workspace_diagnostic},
document_sync::{
did_change_text_document, did_close_text_document, did_open_text_document,
text_document_sync_capability,
},
semantic_tokens::{semantic_tokens_capabilities, semantic_tokens_full},
},
};
use super::error::LspResult;
pub struct Server {
pub(super) proj: Project,
pub(super) open_documents: HashMap<Url, FileHandle>,
}
impl Server {
pub fn initialize(
connection: &lsp_server::Connection,
) -> std::result::Result<Self, Either<lsp_server::ProtocolError, serde_json::Error>> {
let server_capabilities = serde_json::to_value(&lsp_types::ServerCapabilities {
text_document_sync: Some(text_document_sync_capability()),
diagnostic_provider: Some(diagnostic_capabilities()),
semantic_tokens_provider: Some(semantic_tokens_capabilities()),
definition_provider: Some(definition_capabilites()),
references_provider: Some(references_capabilities()),
document_highlight_provider: Some(document_highlight_capabilities()),
completion_provider: Some(completion_capabilities()),
document_symbol_provider: Some(document_symbol_capabilities()),
workspace_symbol_provider: Some(workspace_symbol_capabilities()),
folding_range_provider: Some(folding_range_capabilities()),
rename_provider: Some(rename_capabilities()),
..Default::default()
})
.expect("malformed ServerCapabilities");
info!("Waiting for 'initialize' call from client");
let (id, params) = connection.initialize_start().map_err(Either::Left)?;
info!("Got 'initialize' from client");
debug!("initialize client params: {params}");
let initialize_data = serde_json::json!({
"capabilities": server_capabilities,
});
debug!("sending server params: {initialize_data}");
connection
.initialize_finish(id, initialize_data)
.map_err(Either::Left)?;
info!("Got 'initialized' from client");
let initialize_params: lsp_types::InitializeParams =
serde_json::from_value(params).map_err(Either::Right)?;
let root = initialize_params
.workspace_folders
.and_then(|w| w.first().and_then(|w| uri_to_pathbuf(&w.uri)));
let mut proj = Project::new(root);
proj.initialize();
Ok(Self {
proj,
open_documents: HashMap::new(),
})
}
pub fn run(
&mut self,
connection: &lsp_server::Connection,
) -> std::result::Result<(), Box<dyn Error>> {
for msg in &connection.receiver {
match msg {
lsp_server::Message::Request(request)
if connection.handle_shutdown(&request)? =>
{
break;
}
lsp_server::Message::Request(req) => self.handle_request(connection, req)?,
lsp_server::Message::Notification(msg) => self.handle_notification(msg)?,
msg => error!("unknown msg: {msg:?}"),
}
}
Ok(())
}
fn handle_request(
&mut self,
connection: &lsp_server::Connection,
req: lsp_server::Request,
) -> std::result::Result<(), Box<dyn Error>> {
use lsp_types::request::*;
let res = match req.method.as_str() {
DocumentDiagnosticRequest::METHOD => {
self.request_handler::<DocumentDiagnosticRequest>(document_diagnostic, req)
}
WorkspaceDiagnosticRequest::METHOD => {
self.request_handler::<WorkspaceDiagnosticRequest>(workspace_diagnostic, req)
}
SemanticTokensFullRequest::METHOD => {
self.request_handler::<SemanticTokensFullRequest>(semantic_tokens_full, req)
}
GotoDefinition::METHOD => self.request_handler::<GotoDefinition>(goto_definition, req),
References::METHOD => self.request_handler::<References>(references, req),
DocumentHighlightRequest::METHOD => {
self.request_handler::<DocumentHighlightRequest>(document_highlight, req)
}
Completion::METHOD => self.request_handler::<Completion>(completion, req),
DocumentSymbolRequest::METHOD => {
self.request_handler::<DocumentSymbolRequest>(document_symbol, req)
}
WorkspaceSymbolRequest::METHOD => {
self.request_handler::<WorkspaceSymbolRequest>(workspace_symbol, req)
}
FoldingRangeRequest::METHOD => {
self.request_handler::<FoldingRangeRequest>(folding_range, req)
}
PrepareRenameRequest::METHOD => {
self.request_handler::<PrepareRenameRequest>(prepare_rename, req)
}
Rename::METHOD => self.request_handler::<Rename>(rename, req),
_ => {
error!("unknown request: {req:?}");
lsp_server::Response {
id: req.id,
result: None,
error: Some(lsp_server::ResponseError {
code: lsp_server::ErrorCode::MethodNotFound as i32,
message: "unknown LSP request method".into(),
data: None,
}),
}
}
};
connection.sender.send(lsp_server::Message::Response(res))?;
Ok(())
}
fn handle_notification(
&mut self,
msg: lsp_server::Notification,
) -> std::result::Result<(), Box<dyn Error>> {
use lsp_types::notification::*;
match msg.method.as_str() {
DidOpenTextDocument::METHOD => {
self.notification_handler::<DidOpenTextDocument>(did_open_text_document, msg);
}
DidChangeTextDocument::METHOD => {
self.notification_handler::<DidChangeTextDocument>(did_change_text_document, msg);
}
DidCloseTextDocument::METHOD => {
self.notification_handler::<DidCloseTextDocument>(did_close_text_document, msg);
}
_ => {
warn!("unknown notification: {msg:?}");
}
};
Ok(())
}
fn request_handler<R>(
&mut self,
f: impl Fn(&mut Server, R::Params) -> LspResult<R::Result>,
req: lsp_server::Request,
) -> lsp_server::Response
where
R: lsp_types::request::Request,
{
let (id, params) = req
.extract::<R::Params>(R::METHOD)
.expect("request_handler called with incorrect method ID");
let res = ((f)(self, params)).and_then(|r| serde_json::to_value(r).map_err(Into::into));
match res {
Ok(result) => lsp_server::Response {
id,
result: Some(result),
error: None,
},
Err(error) => {
error!("Error handling request {}: {error}", R::METHOD);
lsp_server::Response {
id,
result: None,
error: Some(lsp_server::ResponseError {
code: error.code() as i32,
message: error.to_string(),
data: None,
}),
}
}
}
}
fn notification_handler<N>(
&mut self,
f: impl Fn(&mut Server, N::Params) -> std::result::Result<(), Box<dyn std::error::Error>>,
msg: lsp_server::Notification,
) where
N: lsp_types::notification::Notification,
{
let params = msg
.extract::<N::Params>(N::METHOD)
.expect("notification_handler called with incorrect method ID");
let res = (f)(self, params);
match res {
Ok(_) => (),
Err(error) => error!("Error handling notification {}: {error}", N::METHOD),
}
}
pub fn uri_to_file(&self, uri: &Url) -> LspResult<FileHandle> {
if let Some(file) = self.open_documents.get(uri) {
return Ok(*file);
}
if let Some(file) = uri_to_pathbuf(uri) {
let file = self.proj.lookup_file(&file);
return Ok(file);
}
Err(LspErrorString(format!("Unknown URI '{}'", *uri), ErrorCode::InvalidParams).into())
}
pub fn file_to_uri(&self, file: FileHandle) -> LspResult<Url> {
if let Some((uri, _)) = self.open_documents.iter().find(|(_, v)| **v == file) {
return Ok(uri.clone());
}
if let Some(path) = &self.proj.get_file(file).path {
return path_to_uri(path);
}
error!("Non local file not in open_documents");
Err(LspErrorString(
"Non local file not in open_documents".to_string(),
ErrorCode::InternalError,
)
.into())
}
pub fn uri_to_pkg(&mut self, uri: &Url) -> LspResult<PkgHandle> {
let file = self.uri_to_file(uri)?;
Ok(self.proj.pkg_for_file(file))
}
}
pub fn uri_to_pathbuf(uri: &Url) -> Option<PathBuf> {
if uri.scheme() != "file" {
return None;
}
uri.to_file_path().ok()
}
pub fn path_to_uri(path: &Path) -> LspResult<Url> {
Url::from_file_path(path).map_err(|_| {
LspErrorString(
format!("Failed to constuct URI from path '{}'", path.display()),
ErrorCode::InternalError,
)
.into()
})
}