use std::sync::Arc;
use anyhow::Result;
use dashmap::DashMap;
use tower_lsp::lsp_types::{
CreateFilesParams, DeleteFilesParams, Diagnostic, DidChangeConfigurationParams, DidChangeTextDocumentParams,
DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
DidOpenTextDocumentParams, DidSaveTextDocumentParams, InitializedParams, MessageType, RenameFilesParams,
TextDocumentItem, WorkspaceFolder,
};
use crate::fs::FileSystem;
#[async_trait::async_trait]
pub trait Backend: Clone + Send + Sync
where
Self: 'static,
{
fn client(&self) -> &tower_lsp::Client;
fn fs(&self) -> &Arc<crate::fs::FileManager>;
async fn is_initialized(&self) -> bool;
async fn set_is_initialized(&self, is_initialized: bool);
async fn workspace_folders(&self) -> Vec<WorkspaceFolder>;
async fn add_workspace_folders(&self, folders: Vec<WorkspaceFolder>);
async fn remove_workspace_folders(&self, folders: Vec<WorkspaceFolder>);
fn code_map(&self) -> &DashMap<String, Vec<u8>>;
async fn insert_code_map(&self, uri: String, text: Vec<u8>);
async fn remove_from_code_map(&self, uri: String) -> Option<Vec<u8>>;
async fn clear_code_state(&self);
fn current_diagnostics_map(&self) -> &DashMap<String, Vec<Diagnostic>>;
async fn inner_on_change(&self, params: TextDocumentItem, force: bool);
async fn has_diagnostics(&self, uri: &str) -> bool {
let Some(diagnostics) = self.current_diagnostics_map().get(uri) else {
return false;
};
!diagnostics.is_empty()
}
async fn on_change(&self, params: TextDocumentItem) {
let filename = params.uri.to_string();
if let Some(current_code) = self.code_map().get(&filename) {
if *current_code == params.text.as_bytes() && !self.has_diagnostics(&filename).await {
return;
}
}
self.insert_code_map(params.uri.to_string(), params.text.as_bytes().to_vec())
.await;
self.inner_on_change(params, false).await;
}
async fn update_from_disk<P: AsRef<std::path::Path> + std::marker::Send>(&self, path: P) -> Result<()> {
let files = self.fs().get_all_files(path.as_ref(), Default::default()).await?;
for file in files {
let contents = self.fs().read(&file, Default::default()).await?;
let file_path = format!(
"file://{}",
file.as_path()
.to_str()
.ok_or_else(|| anyhow::anyhow!("could not get name of file: {:?}", file))?
);
self.insert_code_map(file_path, contents).await;
}
Ok(())
}
async fn do_initialized(&self, params: InitializedParams) {
self.client()
.log_message(MessageType::INFO, format!("initialized: {:?}", params))
.await;
self.set_is_initialized(true).await;
}
async fn do_shutdown(&self) -> tower_lsp::jsonrpc::Result<()> {
self.client()
.log_message(MessageType::INFO, "shutdown".to_string())
.await;
Ok(())
}
async fn do_did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
let should_clear = if !params.event.added.is_empty() {
let mut should_clear = false;
for folder in params.event.added.iter() {
if !self
.workspace_folders()
.await
.iter()
.any(|f| f.uri == folder.uri && f.name == folder.name)
{
should_clear = true;
break;
}
}
should_clear
} else {
!(params.event.removed.is_empty() && params.event.added.is_empty())
};
self.add_workspace_folders(params.event.added.clone()).await;
self.remove_workspace_folders(params.event.removed).await;
if !self.code_map().is_empty() && should_clear {
self.clear_code_state().await;
}
for added in params.event.added {
let project_dir = added.uri.to_string().replace("file://", "");
if let Err(err) = self.update_from_disk(&project_dir).await {
self.client()
.log_message(
MessageType::WARNING,
format!("updating from disk `{}` failed: {:?}", project_dir, err),
)
.await;
}
}
}
async fn do_did_change_configuration(&self, params: DidChangeConfigurationParams) {
self.client()
.log_message(MessageType::INFO, format!("configuration changed: {:?}", params))
.await;
}
async fn do_did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
self.client()
.log_message(MessageType::INFO, format!("watched files changed: {:?}", params))
.await;
}
async fn do_did_create_files(&self, params: CreateFilesParams) {
self.client()
.log_message(MessageType::INFO, format!("files created: {:?}", params))
.await;
for file in params.files {
self.insert_code_map(file.uri.to_string(), Default::default()).await;
}
}
async fn do_did_rename_files(&self, params: RenameFilesParams) {
self.client()
.log_message(MessageType::INFO, format!("files renamed: {:?}", params))
.await;
for file in params.files {
if let Some(value) = self.remove_from_code_map(file.old_uri).await {
self.insert_code_map(file.new_uri.to_string(), value).await;
} else {
self.insert_code_map(file.new_uri.to_string(), Default::default()).await;
}
}
}
async fn do_did_delete_files(&self, params: DeleteFilesParams) {
self.client()
.log_message(MessageType::INFO, format!("files deleted: {:?}", params))
.await;
for file in params.files {
self.remove_from_code_map(file.uri.to_string()).await;
}
}
async fn do_did_open(&self, params: DidOpenTextDocumentParams) {
let new_params = TextDocumentItem {
uri: params.text_document.uri,
text: params.text_document.text,
version: params.text_document.version,
language_id: params.text_document.language_id,
};
self.on_change(new_params).await;
}
async fn do_did_change(&self, mut params: DidChangeTextDocumentParams) {
let new_params = TextDocumentItem {
uri: params.text_document.uri,
text: std::mem::take(&mut params.content_changes[0].text),
version: params.text_document.version,
language_id: Default::default(),
};
self.on_change(new_params).await;
}
async fn do_did_save(&self, params: DidSaveTextDocumentParams) {
if let Some(text) = params.text {
let new_params = TextDocumentItem {
uri: params.text_document.uri,
text,
version: Default::default(),
language_id: Default::default(),
};
self.on_change(new_params).await;
}
}
async fn do_did_close(&self, params: DidCloseTextDocumentParams) {
self.client()
.log_message(MessageType::INFO, format!("document closed: {:?}", params))
.await;
}
}