Step 6: Implement the Ecosystem Trait
Note: everything below that touches
tower-lsp-servertypes (Range,CompletionItem, thegenerate_completions/complete_versionmethods) belongs behind this crate’slsp-responsesfeature (see Step 1) in the real implementation — e.g.#[cfg(feature = "lsp-responses")] use tower_lsp_server::ls_types::Range;. Omitted from the snippet below for readability; seecrates/deps-maven/src/ecosystem.rsfor the full#[cfg(feature = "lsp-responses")]-gated shape.
Create the main ecosystem implementation in ecosystem.rs:
#![allow(unused)]
fn main() {
//! {Ecosystem} implementation for deps-lsp.
use std::any::Any;
use std::sync::Arc;
use tower_lsp_server::ls_types::Range;
use url::Url;
use deps_core::{
Ecosystem, HttpCache, PackageName, ParseResult as ParseResultTrait, Registry, Result,
completion::{Completions, CompletionRequest, complete_package_names_generic, complete_versions_at_position},
ecosystem::BoxFuture,
lockfile::LockFileProvider,
lsp_helpers::EcosystemFormatter,
};
use crate::formatter::{Ecosystem}Formatter;
use crate::lockfile::{Ecosystem}LockfileParser;
use crate::parser::parse_{manifest};
use crate::registry::{Ecosystem}Registry;
/// {Ecosystem} ecosystem implementation.
pub struct {Ecosystem}Ecosystem {
registry: Arc<{Ecosystem}Registry>,
formatter: {Ecosystem}Formatter,
}
impl {Ecosystem}Ecosystem {
pub fn new(cache: Arc<HttpCache>) -> Self {
Self {
registry: Arc::new({Ecosystem}Registry::new(cache)),
formatter: {Ecosystem}Formatter,
}
}
}
// Required sealed trait impl — a documented contract (code review, not the compiler)
// restricting `Ecosystem` implementations to crates in this workspace; see
// `deps_core::ecosystem::private`'s doc for why Rust cannot enforce this any harder.
impl deps_core::ecosystem::private::Sealed for {Ecosystem}Ecosystem {}
impl Ecosystem for {Ecosystem}Ecosystem {
fn ecosystem_id(&self) -> deps_core::EcosystemId {
deps_core::EcosystemId::{Ecosystem}
}
fn display_name(&self) -> &'static str {
"{Ecosystem Name}"
}
fn manifest_filenames(&self) -> &[&'static str] {
&["{manifest_filename}"]
}
fn lockfile_filenames(&self) -> &[&'static str] {
&["{lockfile_filename}"]
}
fn parse_manifest<'a>(
&'a self,
content: &'a str,
uri: &'a Url,
) -> BoxFuture<'a, Result<Box<dyn ParseResultTrait>>> {
Box::pin(async move {
let result = parse_{manifest}(content, uri)?;
Ok(Box::new(result) as Box<dyn ParseResultTrait>)
})
}
fn registry(&self) -> Arc<dyn Registry> {
self.registry.clone() as Arc<dyn Registry>
}
fn lockfile_provider(&self) -> Option<Arc<dyn LockFileProvider>> {
Some(Arc::new({Ecosystem}LockfileParser))
}
fn formatter(&self) -> &dyn EcosystemFormatter {
&self.formatter
}
// generate_inlay_hints, generate_hover, generate_code_actions, generate_diagnostics,
// generate_completions all have default implementations in the Ecosystem trait that
// delegate to lsp_helpers / dispatch to the complete_* hooks below. Override only if
// custom behavior is needed (issue #793: generate_completions's default is the
// exhaustive match over CompletionContext — do NOT hand-write that match again in a
// new ecosystem crate; implement the three hooks it dispatches to instead).
// Required — every ecosystem serves version completion, and this is the one hook with
// no default. Resolved by cursor position, not by name (issue #593):
// complete_versions_at_position re-derives the dependency at `request.position` from
// `request.parse_result` and gates the lookup on that dependency's own
// `Dependency::source()` via the formatter's `SourcePolicy::can_resolve_source` — do NOT
// call a registry directly from `package_name`/`prefix` alone (issue #1136: doing so
// skips that gate and can leak a private/non-registry dependency's name to the public
// registry on every keystroke).
fn complete_version<'a>(
&'a self,
request: CompletionRequest<'a>,
_package_name: PackageName,
prefix: String,
) -> BoxFuture<'a, Completions> {
Box::pin(async move {
complete_versions_at_position(
self.registry.as_ref(),
&self.formatter,
request.parse_result,
request.position,
&prefix,
&[],
request.freshness,
)
.await
.into()
})
}
// Optional — default is `Completions::default()` (no package-name search), correct
// for an ecosystem with no package-name index. Override to search a registry:
//
// fn complete_package_name<'a>(
// &'a self,
// _request: CompletionRequest<'a>,
// prefix: String,
// range: Range,
// ) -> BoxFuture<'a, Completions> {
// Box::pin(async move {
// complete_package_names_generic(self.registry.as_ref(), &prefix, 20, range)
// .await
// .into()
// })
// }
//
// `is_incomplete` should stay `false` unless this ecosystem serves completions from a
// capped/unranked index (see PyPI's `complete_package_name` override, which returns
// `Completions::new(items).with_incomplete(true)`).
// Optional — default is `Completions::default()` (no feature-flag concept). Override
// only for a manifest format with a feature/flag array (e.g. Cargo, Go).
// Raw-text fallback completion (parse-failure path, issue #722): override
// `fallback_completion_prefix` only if this manifest format has a cheap raw-text
// section boundary to detect (most do — see e.g. `deps_core::fallback_completion`'s
// shared TOML/JSON/XML-tag scanners). Default `None` disables fallback completion
// for this ecosystem, which is correct if there is none (e.g. a manifest format
// with no delimited dependencies section).
//
// `completion_insert_text` is REQUIRED — no default — since a missing override
// would silently insert another ecosystem's manifest syntax (issue #118's failure
// mode). Called only from the raw-text fallback path above — the primary (parsed)
// completion path builds its own insert text via
// `build_package_completion`/`complete_package_names_generic` and never calls this.
fn completion_insert_text(&self, metadata: &dyn deps_core::Metadata) -> Option<String> {
Some(format!("\"{}\" = \"{}\"", metadata.name(), metadata.latest_version()))
}
fn as_any(&self) -> &dyn Any {
self
}
}
}