use serde_json::Value;
use std::sync::Arc;
use tower_lsp_server::LanguageServer;
use tower_lsp_server::jsonrpc::Result;
use tower_lsp_server::ls_types::*;
use super::{PanacheLsp, documents, handlers};
fn watched_document_glob() -> Vec<FileSystemWatcher> {
crate::all_document_extensions()
.iter()
.map(|ext| FileSystemWatcher {
glob_pattern: GlobPattern::String(format!("**/*.{ext}")),
kind: Some(WatchKind::all()),
})
.collect()
}
fn file_operation_filters() -> Vec<FileOperationFilter> {
crate::all_document_extensions()
.iter()
.map(|ext| FileOperationFilter {
scheme: Some("file".to_string()),
pattern: FileOperationPattern {
glob: format!("**/*.{ext}"),
matches: Some(FileOperationPatternKind::File),
options: None,
},
})
.collect()
}
fn legacy_root_uri(params: &InitializeParams) -> Option<Uri> {
let value = serde_json::to_value(params).ok()?;
value
.get("rootUri")
.cloned()
.and_then(|root_uri| serde_json::from_value(root_uri).ok())
}
fn experimental_incremental_parsing_from_initialize(params: &InitializeParams) -> bool {
fn get_bool(value: &Value, path: &[&str]) -> Option<bool> {
let mut current = value;
for key in path {
current = current.get(key)?;
}
current.as_bool()
}
let Some(options) = params.initialization_options.as_ref() else {
return false;
};
get_bool(
options,
&["settings", "panache", "experimental", "incrementalParsing"],
)
.or_else(|| get_bool(options, &["panache", "experimental", "incrementalParsing"]))
.or_else(|| get_bool(options, &["experimental", "incrementalParsing"]))
.unwrap_or(false)
}
impl LanguageServer for PanacheLsp {
async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
if let Some(folders) = params.workspace_folders.as_ref()
&& let Some(folder) = folders.first()
&& let Some(path) = folder.uri.to_file_path()
{
*self.workspace_root.lock().await = Some(path.into_owned());
} else if let Some(root_uri) = legacy_root_uri(¶ms)
&& let Some(path) = root_uri.to_file_path()
{
*self.workspace_root.lock().await = Some(path.into_owned());
}
let experimental_incremental = experimental_incremental_parsing_from_initialize(¶ms);
{
let mut runtime_settings = self.runtime_settings.lock().await;
runtime_settings.experimental_incremental_parsing = experimental_incremental;
}
log::debug!(
"lsp runtime setting experimental.incrementalParsing={} (initialize options)",
experimental_incremental
);
Ok(InitializeResult {
capabilities: ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Options(
TextDocumentSyncOptions {
open_close: Some(true),
change: Some(TextDocumentSyncKind::INCREMENTAL),
..Default::default()
},
)),
document_formatting_provider: Some(OneOf::Left(true)),
document_range_formatting_provider: Some(OneOf::Left(true)),
code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
document_symbol_provider: Some(OneOf::Left(true)),
document_link_provider: Some(DocumentLinkOptions {
resolve_provider: Some(true),
work_done_progress_options: WorkDoneProgressOptions {
work_done_progress: None,
},
}),
folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
definition_provider: Some(OneOf::Left(true)),
hover_provider: Some(HoverProviderCapability::Simple(true)),
completion_provider: Some(CompletionOptions::default()),
references_provider: Some(OneOf::Left(true)),
workspace_symbol_provider: Some(OneOf::Left(true)),
rename_provider: Some(OneOf::Right(RenameOptions {
prepare_provider: Some(true),
work_done_progress_options: WorkDoneProgressOptions {
work_done_progress: None,
},
})),
workspace: Some(WorkspaceServerCapabilities {
workspace_folders: Some(WorkspaceFoldersServerCapabilities {
supported: Some(true),
change_notifications: Some(OneOf::Left(true)),
}),
file_operations: Some(WorkspaceFileOperationsServerCapabilities {
will_rename: Some(FileOperationRegistrationOptions {
filters: file_operation_filters(),
}),
..Default::default()
}),
}),
..Default::default()
},
server_info: Some(ServerInfo {
name: "panache-lsp".to_string(),
version: Some(env!("CARGO_PKG_VERSION").to_string()),
}),
..Default::default()
})
}
async fn initialized(&self, _: InitializedParams) {
self.client
.log_message(MessageType::INFO, "panache LSP server initialized")
.await;
log::debug!("initialized LSP server");
if let Ok(options) = serde_json::to_value(DidChangeWatchedFilesRegistrationOptions {
watchers: {
let mut watchers = vec![
FileSystemWatcher {
glob_pattern: GlobPattern::String("**/*.bib".to_string()),
kind: Some(WatchKind::all()),
},
FileSystemWatcher {
glob_pattern: GlobPattern::String("**/*.json".to_string()),
kind: Some(WatchKind::all()),
},
FileSystemWatcher {
glob_pattern: GlobPattern::String("**/*.yaml".to_string()),
kind: Some(WatchKind::all()),
},
FileSystemWatcher {
glob_pattern: GlobPattern::String("**/*.yml".to_string()),
kind: Some(WatchKind::all()),
},
FileSystemWatcher {
glob_pattern: GlobPattern::String("**/*.ris".to_string()),
kind: Some(WatchKind::all()),
},
];
watchers.extend(watched_document_glob());
watchers
},
}) {
let registrations = vec![Registration {
id: "watch-bibliography-files".to_string(),
method: "workspace/didChangeWatchedFiles".to_string(),
register_options: Some(options),
}];
if let Err(e) = self.client.register_capability(registrations).await {
self.client
.log_message(
MessageType::WARNING,
format!("Failed to register file watchers: {:?}", e),
)
.await;
}
}
}
async fn shutdown(&self) -> Result<()> {
Ok(())
}
async fn did_open(&self, params: DidOpenTextDocumentParams) {
documents::did_open(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.workspace_root),
Arc::clone(&self.salsa_db),
params,
)
.await;
}
async fn did_change(&self, params: DidChangeTextDocumentParams) {
documents::did_change(
Arc::clone(&self.document_map),
Arc::clone(&self.workspace_root),
Arc::clone(&self.salsa_db),
Arc::clone(&self.runtime_settings),
&self.client,
params,
)
.await;
}
async fn did_close(&self, params: DidCloseTextDocumentParams) {
documents::did_close(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
params,
)
.await;
}
async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
handlers::formatting::format_document(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
Arc::clone(&self.workspace_root),
params,
)
.await
}
async fn range_formatting(
&self,
params: DocumentRangeFormattingParams,
) -> Result<Option<Vec<TextEdit>>> {
handlers::formatting::format_range(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
Arc::clone(&self.workspace_root),
params,
)
.await
}
async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
handlers::code_actions::code_action(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
Arc::clone(&self.workspace_root),
params,
)
.await
}
async fn document_symbol(
&self,
params: DocumentSymbolParams,
) -> Result<Option<DocumentSymbolResponse>> {
handlers::document_symbols::document_symbol(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
Arc::clone(&self.workspace_root),
params,
)
.await
}
async fn document_link(&self, params: DocumentLinkParams) -> Result<Option<Vec<DocumentLink>>> {
handlers::document_links::document_links(
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
params,
)
.await
}
async fn document_link_resolve(&self, params: DocumentLink) -> Result<DocumentLink> {
handlers::document_links::document_link_resolve(params).await
}
async fn folding_range(&self, params: FoldingRangeParams) -> Result<Option<Vec<FoldingRange>>> {
handlers::folding_ranges::folding_range(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
Arc::clone(&self.workspace_root),
params,
)
.await
}
async fn goto_definition(
&self,
params: GotoDefinitionParams,
) -> Result<Option<GotoDefinitionResponse>> {
handlers::goto_definition::goto_definition(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
Arc::clone(&self.workspace_root),
params,
)
.await
}
async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
handlers::hover::hover(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
Arc::clone(&self.workspace_root),
params,
)
.await
}
async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
handlers::completion::completion(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
Arc::clone(&self.workspace_root),
params,
)
.await
}
async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
log::debug!(
"lsp.rename request uri={:?} line={} char={}",
params.text_document_position.text_document.uri,
params.text_document_position.position.line,
params.text_document_position.position.character
);
handlers::rename::rename(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
Arc::clone(&self.workspace_root),
params,
)
.await
}
async fn prepare_rename(
&self,
params: TextDocumentPositionParams,
) -> Result<Option<PrepareRenameResponse>> {
log::debug!(
"lsp.prepareRename request uri={:?} line={} char={}",
params.text_document.uri,
params.position.line,
params.position.character
);
handlers::prepare_rename::prepare_rename(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
Arc::clone(&self.workspace_root),
params,
)
.await
}
async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
handlers::references::references(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
Arc::clone(&self.workspace_root),
params,
)
.await
}
async fn symbol(
&self,
params: WorkspaceSymbolParams,
) -> Result<Option<WorkspaceSymbolResponse>> {
handlers::workspace_symbols::workspace_symbol(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
Arc::clone(&self.workspace_root),
params,
)
.await
}
async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
handlers::file_watcher::did_change_watched_files(
&self.client,
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
Arc::clone(&self.workspace_root),
params,
)
.await;
}
async fn will_rename_files(&self, params: RenameFilesParams) -> Result<Option<WorkspaceEdit>> {
handlers::file_rename::will_rename_files(
Arc::clone(&self.document_map),
Arc::clone(&self.salsa_db),
Arc::clone(&self.workspace_root),
params,
)
.await
}
}