use std::any::Any;
use std::pin::Pin;
use std::sync::Arc;
use tower_lsp_server::ls_types::{
CodeAction, CompletionItem, Diagnostic, Hover, InlayHint, Position, Uri,
};
use crate::{Registry, lsp_helpers::EcosystemFormatter};
pub mod private {
pub trait Sealed {}
}
pub type BoxFuture<'a, T> = Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>;
pub trait ParseResult: Send + Sync {
fn dependencies(&self) -> Vec<&dyn Dependency>;
fn workspace_root(&self) -> Option<&std::path::Path>;
fn uri(&self) -> &Uri;
fn as_any(&self) -> &dyn Any;
}
pub trait Dependency: Send + Sync {
fn name(&self) -> &str;
fn name_range(&self) -> tower_lsp_server::ls_types::Range;
fn version_requirement(&self) -> Option<&str>;
fn version_range(&self) -> Option<tower_lsp_server::ls_types::Range>;
fn source(&self) -> crate::parser::DependencySource;
fn features(&self) -> &[String] {
&[]
}
fn features_range(&self) -> Option<tower_lsp_server::ls_types::Range> {
None
}
fn as_any(&self) -> &dyn Any;
}
#[derive(Debug, Clone)]
pub struct EcosystemConfig {
pub show_up_to_date_hints: bool,
pub up_to_date_text: String,
pub needs_update_text: String,
pub loading_text: String,
pub show_loading_hints: bool,
}
impl Default for EcosystemConfig {
fn default() -> Self {
Self {
show_up_to_date_hints: true,
up_to_date_text: "✅".to_string(),
needs_update_text: "❌ {}".to_string(),
loading_text: "⏳".to_string(),
show_loading_hints: true,
}
}
}
pub trait Ecosystem: Send + Sync + private::Sealed {
fn id(&self) -> &'static str;
fn display_name(&self) -> &'static str;
fn manifest_filenames(&self) -> &[&'static str];
fn lockfile_filenames(&self) -> &[&'static str] {
&[]
}
fn parse_manifest<'a>(
&'a self,
content: &'a str,
uri: &'a Uri,
) -> BoxFuture<'a, crate::error::Result<Box<dyn ParseResult>>>;
fn registry(&self) -> Arc<dyn Registry>;
fn lockfile_provider(&self) -> Option<Arc<dyn crate::lockfile::LockFileProvider>> {
None
}
fn formatter(&self) -> &dyn EcosystemFormatter;
fn generate_inlay_hints<'a>(
&'a self,
parse_result: &'a dyn ParseResult,
cached_versions: &'a std::collections::HashMap<String, String>,
resolved_versions: &'a std::collections::HashMap<String, String>,
loading_state: crate::LoadingState,
config: &'a EcosystemConfig,
) -> BoxFuture<'a, Vec<InlayHint>> {
Box::pin(async move {
crate::lsp_helpers::generate_inlay_hints(
parse_result,
cached_versions,
resolved_versions,
loading_state,
config,
self.formatter(),
)
})
}
fn generate_hover<'a>(
&'a self,
parse_result: &'a dyn ParseResult,
position: Position,
cached_versions: &'a std::collections::HashMap<String, String>,
resolved_versions: &'a std::collections::HashMap<String, String>,
) -> BoxFuture<'a, Option<Hover>> {
Box::pin(async move {
let registry = self.registry();
crate::lsp_helpers::generate_hover(
parse_result,
position,
cached_versions,
resolved_versions,
registry.as_ref(),
self.formatter(),
)
.await
})
}
fn generate_code_actions<'a>(
&'a self,
parse_result: &'a dyn ParseResult,
position: Position,
_cached_versions: &'a std::collections::HashMap<String, String>,
uri: &'a Uri,
) -> BoxFuture<'a, Vec<CodeAction>> {
Box::pin(async move {
let registry = self.registry();
crate::lsp_helpers::generate_code_actions(
parse_result,
position,
uri,
registry.as_ref(),
self.formatter(),
)
.await
})
}
fn generate_diagnostics<'a>(
&'a self,
parse_result: &'a dyn ParseResult,
cached_versions: &'a std::collections::HashMap<String, String>,
resolved_versions: &'a std::collections::HashMap<String, String>,
_uri: &'a Uri,
) -> BoxFuture<'a, Vec<Diagnostic>> {
Box::pin(async move {
crate::lsp_helpers::generate_diagnostics_from_cache(
parse_result,
cached_versions,
resolved_versions,
self.formatter(),
)
})
}
fn generate_completions<'a>(
&'a self,
parse_result: &'a dyn ParseResult,
position: Position,
content: &'a str,
) -> BoxFuture<'a, Vec<CompletionItem>>;
fn as_any(&self) -> &dyn Any;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ecosystem_config_default() {
let config = EcosystemConfig::default();
assert!(config.show_up_to_date_hints);
assert_eq!(config.up_to_date_text, "✅");
assert_eq!(config.needs_update_text, "❌ {}");
}
#[test]
fn test_ecosystem_config_custom() {
let config = EcosystemConfig {
show_up_to_date_hints: false,
up_to_date_text: "OK".to_string(),
needs_update_text: "Update to {}".to_string(),
loading_text: "Loading...".to_string(),
show_loading_hints: false,
};
assert!(!config.show_up_to_date_hints);
assert_eq!(config.up_to_date_text, "OK");
assert_eq!(config.needs_update_text, "Update to {}");
}
#[test]
fn test_ecosystem_config_clone() {
let config1 = EcosystemConfig::default();
let config2 = config1.clone();
assert_eq!(config1.up_to_date_text, config2.up_to_date_text);
assert_eq!(config1.show_up_to_date_hints, config2.show_up_to_date_hints);
assert_eq!(config1.needs_update_text, config2.needs_update_text);
}
#[test]
fn test_dependency_default_features() {
struct MockDep;
impl Dependency for MockDep {
fn name(&self) -> &'static str {
"test"
}
fn name_range(&self) -> tower_lsp_server::ls_types::Range {
tower_lsp_server::ls_types::Range::default()
}
fn version_requirement(&self) -> Option<&str> {
None
}
fn version_range(&self) -> Option<tower_lsp_server::ls_types::Range> {
None
}
fn source(&self) -> crate::parser::DependencySource {
crate::parser::DependencySource::Registry
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
let dep = MockDep;
assert_eq!(dep.features(), &[] as &[String]);
}
}