pub struct ToolRouter { /* private fields */ }Expand description
Resolves (language, tool) to the ServerId that should handle it.
Built once at startup by Self::from_configs over the applicable
(post-heuristics) server configs. The table in use is derived from that
immutable original by Self::rebind each time a server settles, so no
route ever points at a server known to have failed to spawn.
Implementations§
Source§impl ToolRouter
impl ToolRouter
Sourcepub fn from_configs<'a, I>(cfgs: I) -> Result<Self>where
I: IntoIterator<Item = &'a LspServerConfig>,
pub fn from_configs<'a, I>(cfgs: I) -> Result<Self>where
I: IntoIterator<Item = &'a LspServerConfig>,
Build a router from the configs applicable in this workspace, enforcing the workspace-scoped validation rules:
- No two applicable servers (in any language) may share a
ServerId— it is the key of every map keyed by server identity. - No two applicable servers for one language may both omit
handles(two catch-alls). - No tool may be claimed via
handlesby two applicable servers of the same language.
Also emits a tracing::warn! for any language whose union of
handles claims is partial and has no catch-all server, naming the
tools nobody will serve.
§Errors
Returns crate::error::Error::Config naming the conflicting entries if any
of the three rules above is violated.
Sourcepub fn catch_all<I>(entries: I) -> Self
pub fn catch_all<I>(entries: I) -> Self
Build a router where every entry is a catch-all for its language.
Test helper: takes (id, language) pairs rather than a single entry
because some tests (e.g. the typescript/typescriptreact exact-match
preference) need two catch-alls registered at once.
Sourcepub fn rebind(&mut self, settlement: impl Fn(&ServerId) -> ServerSettlement)
pub fn rebind(&mut self, settlement: impl Fn(&ServerId) -> ServerSettlement)
Derives the active routes from the configured ones and the current
ServerSettlement of every server; a pure function of those, so the
result never depends on the order in which servers settled, and once
none is Pending it equals what a single batch rebind produces.
Per language:
- an explicit route to a
RegisteredorPendingserver is kept; - an explicit route to a
Failedserver is rebound to the language’s catch-all when that isRegistered, kept pointing at the failed server while the catch-all isPending(the route is never bound to a server that has not registered), and dropped when the catch-all isFailedor absent; - a
Failedcatch-all is dropped; - a dead route is never rebound to a narrowly-scoped live server: a
server that declared
handles = [...]has explicitly declined every other tool, and conscripting it would override that declaration (and, via the diagnostics cache filter, start caching diagnostics the user deliberately routed away).
The server order used by Self::resolve_any keeps every server that
is not Failed, so a pending server’s position is preserved.
Silent: callers log the server that just settled, not every earlier failure again.
Sourcepub fn rebind_to_registered(&mut self, registered: &HashSet<ServerId>)
pub fn rebind_to_registered(&mut self, registered: &HashSet<ServerId>)
Self::rebind for a finished startup: every server in registered
is Registered, every other one Failed.
Sourcepub fn languages_routed_to(&self, id: &ServerId) -> Vec<LanguageId>
pub fn languages_routed_to(&self, id: &ServerId) -> Vec<LanguageId>
The configured languages with a route (explicit or catch-all) naming
id, sorted.
Sourcepub fn catch_all_for(&self, language_id: &str) -> Option<&ServerId>
pub fn catch_all_for(&self, language_id: &str) -> Option<&ServerId>
The catch-all server configured for language_id, if any.
Sourcepub fn resolve(&self, language_id: &str, tool: ToolKind) -> Option<&ServerId>
pub fn resolve(&self, language_id: &str, tool: ToolKind) -> Option<&ServerId>
Resolve the server that should handle tool for language_id.
Explicit claims win over the language’s catch-all; if neither exists,
returns None.
Sourcepub fn resolve_any(&self, tool: ToolKind) -> Result<&ServerId, NoServerReason>
pub fn resolve_any(&self, tool: ToolKind) -> Result<&ServerId, NoServerReason>
Resolve a server for tool without a specific language — used for
workspace-wide tools like workspace_symbol_search that have no
document to detect a language from.
Resolves in two tiers, in config declaration order:
- the first server that explicitly claims
tool; - else the first catch-all server.
Deliberately does not fall back to “the first server at all” when
neither tier matches: a server with a handles list has explicitly
declined every tool not on it, so forwarding an unclaimed workspace-wide
tool to it anyway would silently violate that declaration. Callers get
NoServerReason instead, distinguishing “nothing configured” from
“something is configured but nothing claims this tool” so they can
report a precise error rather than defaulting to an arbitrary server.
§Errors
Returns NoServerReason::NothingRegistered if no server is
registered at all, or NoServerReason::NoClaimant if servers are
registered but none explicitly claims tool and none is a catch-all.
Sourcepub fn has_language(&self, language_id: &str) -> bool
pub fn has_language(&self, language_id: &str) -> bool
Whether language_id currently has at least one live-or-configured
route (a catch-all or an explicit claim), used to distinguish
NoServerForTool (some server handles this language, just not this
tool) from NoServerForLanguage (nothing does).
Deliberately checks route contents, not just map-key presence: after
rebind_to_registered drops every route for a language whose sole
server failed to spawn, this must go back to false so that language
reports NoServerForLanguage exactly as it did before per-tool
routing existed, not NoServerForTool.
Sourcepub fn configured_languages(&self) -> Vec<LanguageId>
pub fn configured_languages(&self) -> Vec<LanguageId>
Every language this router was built with, sorted, including
languages whose routes Self::rebind_to_registered has since
emptied – so a language whose servers all failed to spawn is still
listed (and can be reported as having no server) rather than vanishing.
§Examples
use mcpls_core::config::{LanguageId, ServerId, ToolRouter};
let router = ToolRouter::catch_all([
(ServerId::from_static("pyright"), LanguageId::from_static("python")),
(ServerId::from_static("rust-analyzer"), LanguageId::from_static("rust")),
]);
assert_eq!(router.configured_languages(), ["python", "rust"]);