pub struct Translator { /* private fields */ }Expand description
Translator handles MCP tool calls by converting them to LSP requests.
All fields use interior mutability so Translator can be shared via a
plain Arc<Translator> with no outer lock: every LSP tool call would
otherwise serialize behind a single mutex for its entire round trip
(including the LSP request timeout), which is the root cause fixed here.
Each field is locked independently and only for the short, synchronous
section that touches it. In particular, the actual LSP request/response
round trip (client.request(...)) always runs with no lock held.
document_tracker is no exception: DocumentTracker locks its own state
per-path internally (see its docs), so prepare_document’s call into
ensure_open never holds a lock shared across unrelated paths or
languages while it does that document’s disk I/O and
textDocument/didOpen/didChange notify.
Implementations§
Source§impl Translator
impl Translator
Sourcepub async fn handle_completions(
&self,
file_path: String,
position: Position,
trigger: Option<String>,
) -> Result<CompletionsResult>
pub async fn handle_completions( &self, file_path: String, position: Position, trigger: Option<String>, ) -> Result<CompletionsResult>
Handle completions request.
§Errors
Returns an error if trigger exceeds the maximum allowed length,
the LSP request fails, the file cannot be opened, the routed server
does not advertise completionProvider support, or the server is
still indexing the workspace (see wait_for_indexing_ready).
Sourcepub async fn handle_signature_help(
&self,
file_path: String,
position: Position,
) -> Result<SignatureHelpResult>
pub async fn handle_signature_help( &self, file_path: String, position: Position, ) -> Result<SignatureHelpResult>
Handle signature help request (textDocument/signatureHelp).
Returns parameter signatures and documentation while typing a function call.
context is omitted (None) — the server infers trigger state from position.
§Errors
Returns an error if the LSP request fails, the file cannot be opened,
or the routed server does not advertise signatureHelpProvider support.
Sourcepub async fn handle_inlay_hints(
&self,
file_path: String,
start: Position,
end: Position,
) -> Result<InlayHintsResult>
pub async fn handle_inlay_hints( &self, file_path: String, start: Position, end: Position, ) -> Result<InlayHintsResult>
Handle inlay hints request (textDocument/inlayHint).
Returns inferred type and parameter annotations the editor would render inline. Output positions are in MCP 1-based form.
§Errors
Returns an error if the LSP request fails, the file cannot be opened,
or the routed server does not advertise inlayHintProvider support.
Source§impl Translator
impl Translator
Sourcepub async fn handle_call_hierarchy_prepare(
&self,
file_path: String,
position: Position,
) -> Result<CallHierarchyPrepareResult>
pub async fn handle_call_hierarchy_prepare( &self, file_path: String, position: Position, ) -> Result<CallHierarchyPrepareResult>
Handle call hierarchy prepare request.
§Errors
Returns an error if the LSP request fails, the file cannot be opened,
or the routed server does not advertise callHierarchyProvider support.
Sourcepub async fn handle_incoming_calls(
&self,
item: Value,
) -> Result<IncomingCallsResult>
pub async fn handle_incoming_calls( &self, item: Value, ) -> Result<IncomingCallsResult>
Handle incoming calls request.
Routing through prepare_gated_document_for_path means this now
stats, reads, and didOpens the item’s own file as a side effect,
even though a call-hierarchy item is opaque per the LSP spec and
needs no open document – accepted for chokepoint/gating
consistency with handle_references and the other whole-workspace
tools; it does mean a replayed item whose file has since been
deleted now fails on that stat instead of just proceeding.
§Errors
Returns an error if the LSP request fails, the item is invalid, the
routed server does not advertise callHierarchyProvider support, or
the server is still indexing the workspace after
INDEXING_READY_TIMEOUT.
Sourcepub async fn handle_outgoing_calls(
&self,
item: Value,
) -> Result<OutgoingCallsResult>
pub async fn handle_outgoing_calls( &self, item: Value, ) -> Result<OutgoingCallsResult>
Handle outgoing calls request.
Same didOpen-as-side-effect trade-off as handle_incoming_calls –
see that method’s doc.
§Errors
Returns an error if the LSP request fails, the item is invalid, the
routed server does not advertise callHierarchyProvider support, or
the server is still indexing the workspace after
INDEXING_READY_TIMEOUT.
Source§impl Translator
impl Translator
Sourcepub fn cached_diagnostics_uri(
workspace_roots: &[PathBuf],
file_path: &str,
) -> Result<String>
pub fn cached_diagnostics_uri( workspace_roots: &[PathBuf], file_path: &str, ) -> Result<String>
Resolve the LSP-side cache key (URI string) for a cached-diagnostics lookup.
Split out from the cache read itself so callers (e.g. the
get_cached_diagnostics MCP tool) can do the path canonicalize() and
workspace-boundary check before taking the NotificationCache lock —
that lock is also needed by diagnostics_pump to store incoming
notifications, so nothing that isn’t a plain map lookup should run
while it’s held.
§Errors
Returns an error if the path is invalid or outside workspace boundaries.
Sourcepub async fn handle_diagnostics(
&self,
file_path: String,
notification_cache: &Mutex<NotificationCache>,
) -> Result<DiagnosticsResult>
pub async fn handle_diagnostics( &self, file_path: String, notification_cache: &Mutex<NotificationCache>, ) -> Result<DiagnosticsResult>
Handle diagnostics request.
Merges the LSP pull-model response (textDocument/diagnostic) with
whatever is already cached from textDocument/publishDiagnostics push
notifications for the same file, so this returns the same diagnostics
get_cached_diagnostics would for the file at the same point in time
(see #244 — rust-analyzer’s pull endpoint omits flycheck/clippy-sourced
diagnostics, and empirically also some native ones, that are only ever
delivered via the push path). If the pull request itself fails (e.g. a
push-only server answering -32601, or a timeout), a non-empty cache
entry is returned as a cache-only result instead of propagating the
error, since the cache is not required to be fresher than the pull
response to be useful here.
The cache is read only after the pull request settles (success or
failure) and held only for the lookup itself — never across the LSP
round-trip — matching the lock-ordering discipline documented on
cached_diagnostics_uri. Like get_cached_diagnostics, the cache is
treated as eventually consistent: a cached entry may reflect a
slightly older document version than the fresh pull result if an edit
landed inside the server’s flycheck debounce window.
Deliberately not gated on workspace-indexing readiness the way
IndexingGate::Required whole-workspace queries are (#445, see
routing::IndexingGate’s doc): this is a poll-based read, not a live
whole-workspace LSP request, so blocking it on
Translator::wait_for_indexing_ready would be the wrong fix shape.
The get_diagnostics MCP tool instead surfaces the routed server’s
indexing state as an explicit indexingInProgress flag alongside this
method’s result.
§Errors
Returns an error if the LSP pull request fails and the cache holds no diagnostics for the file either, or if the file cannot be opened.
Sourcepub async fn diagnostics_from_cache_entry(
diag_info: Option<&DiagnosticInfo>,
encoding: PositionEncoding,
tracker: &Arc<DocumentTracker>,
) -> DiagnosticsResult
pub async fn diagnostics_from_cache_entry( diag_info: Option<&DiagnosticInfo>, encoding: PositionEncoding, tracker: &Arc<DocumentTracker>, ) -> DiagnosticsResult
Convert a cached diagnostics entry into the MCP-facing result shape.
Takes an already-cloned Option<&DiagnosticInfo> (out of the
NotificationCache lock) rather than the cache itself, so this
mapping — which is not a bounded operation for a large diagnostics set
— never runs while the cache is locked.
encoding is the negotiated encoding of the server that published
these diagnostics; pass PositionEncoding::Utf16 when no live server
context is available (e.g. a cache-only read with no resolved owner).
Sourcepub async fn merge_diagnostics(
pull: DiagnosticsResult,
diag_info: Option<&DiagnosticInfo>,
encoding: PositionEncoding,
tracker: &Arc<DocumentTracker>,
) -> DiagnosticsResult
pub async fn merge_diagnostics( pull: DiagnosticsResult, diag_info: Option<&DiagnosticInfo>, encoding: PositionEncoding, tracker: &Arc<DocumentTracker>, ) -> DiagnosticsResult
Merge push-model diagnostics from the notification cache into a
pull-model (textDocument/diagnostic) result.
rust-analyzer’s pull endpoint omits diagnostics that are only ever
delivered via textDocument/publishDiagnostics push notifications —
not just flycheck/clippy lints, but empirically (verified against a
live rust-analyzer 1.97.1 session, see #244) some native diagnostics
too. Those are cached separately in NotificationCache.
Where the same logical problem is reported through both paths, the
two representations were observed to differ in both range and
rendered message. Captured example, a “not all trait items
implemented” (E0046) error for one impl block: pull reported range
(96,7)-(96,12) (the trait name) with message “not all trait items
implemented, missing: fn hello”; the push notification for the same
error reported range (95,1)-(95,32) (the impl block) with message
“not all trait items implemented, missing: hello\nmissing hello
in implementation” — same code/severity, adjacent but distinct
ranges, different message text. Exact field equality never dedups
cases like that.
Given that, a cache entry is treated as a duplicate of a pull entry
when both carry a code, the (severity, code) pair matches, and
the two ranges are either overlapping or start within
DUPLICATE_RANGE_PROXIMITY_LINES lines of each other — close
enough to be the same underlying model divergence, not two distinct
occurrences of the same error class (e.g. two unrelated E0308
mismatches at different call sites in one file, one caught only
natively and one only by flycheck). Diagnostics with no code fall
back to full-field equality, since there is no cheaper stable
identity available for them.
Output is sorted by (start.line, start.character) so merged
cache-only entries don’t land out of document order after the
pull-model ones.
Sourcepub fn handle_server_logs(
cache: &NotificationCache,
limit: usize,
min_level: Option<String>,
) -> Result<ServerLogsResult>
pub fn handle_server_logs( cache: &NotificationCache, limit: usize, min_level: Option<String>, ) -> Result<ServerLogsResult>
Sourcepub fn handle_server_messages(
cache: &NotificationCache,
limit: usize,
) -> Result<ServerMessagesResult>
pub fn handle_server_messages( cache: &NotificationCache, limit: usize, ) -> Result<ServerMessagesResult>
Source§impl Translator
impl Translator
Sourcepub async fn handle_rename(
&self,
file_path: String,
position: Position,
new_name: String,
) -> Result<RenameResult>
pub async fn handle_rename( &self, file_path: String, position: Position, new_name: String, ) -> Result<RenameResult>
Handle rename request.
§Errors
Returns an error if new_name exceeds the maximum allowed length,
the LSP request fails, the file cannot be opened, the routed server
does not advertise renameProvider support, or the server is still
indexing the workspace (see Translator::wait_for_indexing_ready) –
a rename needs the same whole-workspace reference index as
get_references.
Sourcepub async fn handle_format_document(
&self,
file_path: String,
tab_size: u32,
insert_spaces: bool,
) -> Result<FormatDocumentResult>
pub async fn handle_format_document( &self, file_path: String, tab_size: u32, insert_spaces: bool, ) -> Result<FormatDocumentResult>
Handle format document request.
§Errors
Returns an error if the LSP request fails, the file cannot be opened,
or the routed server does not advertise documentFormattingProvider support.
Sourcepub async fn handle_code_actions(
&self,
file_path: String,
start: Position,
end: Position,
kind_filter: Option<String>,
) -> Result<CodeActionsResult>
pub async fn handle_code_actions( &self, file_path: String, start: Position, end: Position, kind_filter: Option<String>, ) -> Result<CodeActionsResult>
Handle code actions request.
For an action returned with data present but edit absent, and
only when the routed server’s codeActionProvider advertises
resolveProvider: true, follows up with a codeAction/resolve
request to populate the edit before returning the action (#432).
§Errors
Returns an error if the LSP request fails, the file cannot be opened,
the routed server does not advertise codeActionProvider support, or
the server is still indexing the workspace (see
wait_for_indexing_ready).
Source§impl Translator
impl Translator
Sourcepub async fn handle_hover(
&self,
file_path: String,
position: Position,
) -> Result<HoverResult>
pub async fn handle_hover( &self, file_path: String, position: Position, ) -> Result<HoverResult>
Handle hover request.
§Errors
Returns an error if the LSP request fails, the file cannot be opened,
the routed server does not advertise hoverProvider support, or the
server is still indexing the workspace after
INDEXING_READY_TIMEOUT.
Sourcepub async fn handle_definition(
&self,
file_path: String,
position: Position,
) -> Result<DefinitionResult>
pub async fn handle_definition( &self, file_path: String, position: Position, ) -> Result<DefinitionResult>
Handle definition request.
§Errors
Returns an error if the LSP request fails, the file cannot be opened,
the routed server does not advertise definitionProvider support, or
the server is still indexing the workspace after
INDEXING_READY_TIMEOUT.
Sourcepub async fn handle_references(
&self,
file_path: String,
position: Position,
include_declaration: bool,
) -> Result<ReferencesResult>
pub async fn handle_references( &self, file_path: String, position: Position, include_declaration: bool, ) -> Result<ReferencesResult>
Handle references request.
§Errors
Returns an error if the LSP request fails, the file cannot be opened,
the routed server does not advertise referencesProvider support, or
the server is still indexing the workspace after
INDEXING_READY_TIMEOUT.
Sourcepub async fn handle_implementation(
&self,
file_path: String,
position: Position,
) -> Result<LocationsResult>
pub async fn handle_implementation( &self, file_path: String, position: Position, ) -> Result<LocationsResult>
Handle go-to-implementation request (textDocument/implementation).
Returns the locations of trait method or interface member implementations.
§Errors
Returns an error if the LSP request fails, the file cannot be opened,
the routed server does not advertise implementationProvider
support, or the server is still indexing the workspace after
INDEXING_READY_TIMEOUT.
Sourcepub async fn handle_type_definition(
&self,
file_path: String,
position: Position,
) -> Result<LocationsResult>
pub async fn handle_type_definition( &self, file_path: String, position: Position, ) -> Result<LocationsResult>
Handle go-to-type-definition request (textDocument/typeDefinition).
Returns the type definition location of the expression at position. Distinct from go-to-definition for variable bindings where definition and type differ.
§Errors
Returns an error if the LSP request fails, the file cannot be opened,
the routed server does not advertise typeDefinitionProvider
support, or the server is still indexing the workspace after
INDEXING_READY_TIMEOUT.
Source§impl Translator
impl Translator
Sourcepub async fn handle_document_symbols(
&self,
file_path: String,
) -> Result<DocumentSymbolsResult>
pub async fn handle_document_symbols( &self, file_path: String, ) -> Result<DocumentSymbolsResult>
Handle document symbols request.
§Errors
Returns an error if the LSP request fails, the file cannot be opened,
or the routed server does not advertise documentSymbolProvider support.
Sourcepub async fn handle_workspace_symbol(
&self,
query: String,
kind_filter: Option<String>,
limit: u32,
) -> Result<WorkspaceSymbolResult>
pub async fn handle_workspace_symbol( &self, query: String, kind_filter: Option<String>, limit: u32, ) -> Result<WorkspaceSymbolResult>
Handle workspace symbol search.
Deliberately not gated on indexing readiness, unlike other
whole-workspace queries (e.g. references, call hierarchy
incoming/outgoing calls): it resolves via resolve_any rather than a
per-file route, so it never goes through prepare_gated_document
(the only chokepoint IndexingGate applies to) at all. Whether/how to
gate it was deferred as a separate open question (spec FR-008) and
remains a known limitation (#423).
§Errors
Returns an error if the LSP request fails, no server is configured, or
the routed server does not advertise workspaceSymbolProvider support.
Source§impl Translator
impl Translator
Sourcepub fn new() -> Self
pub fn new() -> Self
Create a new translator.
Starts with an empty router: nothing is routable until Self::with_router
installs one, which matches having no servers registered.
Also starts with no workspace roots, which makes every path-taking
operation fail closed with Error::NoWorkspaceRoots – embedders
MUST call Self::set_workspace_roots before serving any
path-taking request.
Sourcepub fn set_workspace_roots(&mut self, roots: Vec<PathBuf>)
pub fn set_workspace_roots(&mut self, roots: Vec<PathBuf>)
Set the workspace roots for path validation.
Only called during single-owner setup, before the translator is
shared, so this replaces the Arc wholesale rather than locking.
Mandatory for any embedder that will serve path-taking requests:
leaving roots empty (or never calling this) makes every such
operation reject with Error::NoWorkspaceRoots instead of allowing
unrestricted access.
Sourcepub fn with_notification_cache(
self,
cache: Arc<Mutex<NotificationCache>>,
) -> Self
pub fn with_notification_cache( self, cache: Arc<Mutex<NotificationCache>>, ) -> Self
Give the translator a handle to the shared diagnostics cache, so the respawn path can invalidate a respawned server’s stale entries.
Only called during single-owner setup (mirrors Self::with_router),
before the translator is shared – serve_with passes the same
Arc<Mutex<NotificationCache>> used by the notification pump tasks.
Sourcepub const fn with_indexing_ready_timeout(self, timeout: Duration) -> Self
pub const fn with_indexing_ready_timeout(self, timeout: Duration) -> Self
Override the bound Self::wait_for_indexing_ready waits for a routed
server to report indexing readiness, in place of the built-in
navigation::INDEXING_READY_TIMEOUT default.
Only called during single-owner setup (mirrors Self::with_notification_cache),
before the translator is shared. serve() wires this from
workspace.indexing_ready_timeout_seconds, already range-checked by
crate::config::ServerConfig::validate.
Sourcepub fn set_expected_servers(&self, servers: HashSet<ServerId>)
pub fn set_expected_servers(&self, servers: HashSet<ServerId>)
Mark the set of servers that are expected (configured + applicable) but may still be initializing in the background.
Sourcepub fn clear_expected_servers(&self)
pub fn clear_expected_servers(&self)
Clear the expected-servers set (e.g. after background init failed).
Sourcepub fn with_router(self, router: ToolRouter) -> Self
pub fn with_router(self, router: ToolRouter) -> Self
Install the per-tool routing table built from the applicable configs.
Only called during single-owner setup, before the translator is
shared, so this replaces the Arc-wrapped router wholesale.
Sourcepub fn rebind_router(&self, registered: &HashSet<ServerId>)
pub fn rebind_router(&self, registered: &HashSet<ServerId>)
Rebind the routing table to the set of servers that actually
registered, dropping or redirecting routes to servers that failed to
spawn. See ToolRouter::rebind_to_registered for the full semantics.
Sourcepub fn is_diagnostics_route(&self, language_id: &str, id: &ServerId) -> bool
pub fn is_diagnostics_route(&self, language_id: &str, id: &ServerId) -> bool
Whether id is the server the router currently resolves
ToolKind::Diagnostics to for language_id.
Purpose-built for register_servers, which needs this to compute the
diagnostics-cache filter passed into each pump task, without exposing
the router’s lock guard outside this module.
Sourcepub fn with_extensions(self, extension_map: HashMap<String, String>) -> Self
pub fn with_extensions(self, extension_map: HashMap<String, String>) -> Self
Configure custom file extension mappings.
This method sets the extension map and updates the document tracker to use the same mappings for language detection.
Only called during single-owner setup, before the translator is
shared, so this replaces the Arc-wrapped fields wholesale.
Sourcepub fn with_resource_limits(self, limits: ResourceLimits) -> Self
pub fn with_resource_limits(self, limits: ResourceLimits) -> Self
Configure resource limits (max open documents, max file size) for the document tracker.
Only called during single-owner setup, before the translator is
shared. This builder and Self::with_extensions may be called in
either order – each rebuilds document_tracker from both of
self.resource_limits/self.extension_map’s current values,
instead of one of them starting fresh from
ResourceLimits::default()/an empty extension map, which previously
meant whichever builder ran last silently discarded the other’s
effect.
Sourcepub fn register_client(&self, id: impl Into<ServerId>, client: LspClient)
pub fn register_client(&self, id: impl Into<ServerId>, client: LspClient)
Register an LSP client under its routing identity.
Only called once per server, from register_servers during initial
background init. The respawn path does not reuse this method: it
needs the previous client back (to fail its pending requests) and
must also reset document_tracker for the swapped-in server, neither
of which this method does.
Sourcepub fn register_server(&self, id: impl Into<ServerId>, server: LspServer)
pub fn register_server(&self, id: impl Into<ServerId>, server: LspServer)
Register an LSP server under its routing identity.
Sourcepub fn open_document_paths(&self) -> Vec<PathBuf>
pub fn open_document_paths(&self) -> Vec<PathBuf>
Snapshot of currently open document paths, used for MCP resource listing.
Sourcepub fn is_document_open(&self, path: &Path) -> bool
pub fn is_document_open(&self, path: &Path) -> bool
Whether a document is currently tracked as open.
Trait Implementations§
Source§impl Debug for Translator
impl Debug for Translator
Source§impl Default for Translator
impl Default for Translator
Source§fn default() -> Self
fn default() -> Self
Same as Translator::new: no workspace roots configured, so every
path-taking operation fails closed with Error::NoWorkspaceRoots
until Translator::set_workspace_roots is called.