From db5eab755cdfcf8e614622a65e1269ca91cbd4cc Mon Sep 17 00:00:00 2001 From: ylinwind Date: Tue, 14 Jul 2026 19:26:58 +0800 Subject: [PATCH 1/2] input: add Lsp::on_open_location hook to intercept Go to Definition Hosts can intercept LSP locations before the built-in handling (http(s) opens in browser, otherwise same-document jump) -- e.g. to open a virtual navi-doc:// URI in an in-app docs window. Return true to mark the location handled. --- crates/ui/src/input/lsp/definitions.rs | 17 +++++++++++++---- crates/ui/src/input/lsp/mod.rs | 12 ++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/crates/ui/src/input/lsp/definitions.rs b/crates/ui/src/input/lsp/definitions.rs index 9536603c28..475eebbc27 100644 --- a/crates/ui/src/input/lsp/definitions.rs +++ b/crates/ui/src/input/lsp/definitions.rs @@ -111,7 +111,7 @@ impl InputState { pub(crate) fn on_action_go_to_definition( &mut self, _: &GoToDefinition, - _: &mut Window, + window: &mut Window, cx: &mut Context, ) { let offset = self.cursor(); @@ -121,7 +121,7 @@ impl InputState { } if let Some(location) = locations.first().cloned() { - self.go_to_definition(&location, cx); + self.go_to_definition(&location, window, cx); } } } @@ -131,7 +131,7 @@ impl InputState { &mut self, event: &MouseDownEvent, offset: usize, - _: &mut Window, + window: &mut Window, cx: &mut Context, ) -> bool { if !event.modifiers.secondary() { @@ -149,7 +149,7 @@ impl InputState { return false; }; - self.go_to_definition(&location, cx); + self.go_to_definition(&location, window, cx); true } @@ -157,8 +157,17 @@ impl InputState { pub(crate) fn go_to_definition( &mut self, location: &lsp_types::LocationLink, + window: &mut Window, cx: &mut Context, ) { + // Give the host a chance to handle the location first, e.g. to open + // virtual/external documents (stdlib docs) in an app window. + if let Some(handler) = self.lsp.on_open_location.clone() { + if handler(location, window, cx) { + return; + } + } + if location .target_uri .scheme() diff --git a/crates/ui/src/input/lsp/mod.rs b/crates/ui/src/input/lsp/mod.rs index e42ac6d057..0c6d968d20 100644 --- a/crates/ui/src/input/lsp/mod.rs +++ b/crates/ui/src/input/lsp/mod.rs @@ -19,6 +19,14 @@ pub use document_colors::*; pub use hover::*; pub use semantic_tokens::*; +/// Host hook to intercept opening an LSP location (Go to Definition). +/// +/// Called before the built-in behavior. Return `true` if the host handled +/// the location (e.g. opened a docs window for a virtual/external URI); +/// return `false` to fall through to the default handling (http(s) URIs +/// open in the browser, anything else jumps within the current document). +pub type OpenLocationHandler = Rc bool>; + /// LSP ServerCapabilities /// /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#serverCapabilities @@ -35,6 +43,9 @@ pub struct Lsp { pub document_color_provider: Option>, /// The range semantic tokens provider. pub semantic_tokens_provider: Option>, + /// Optional host hook to intercept Go to Definition locations + /// (see [`OpenLocationHandler`]). + pub on_open_location: Option, document_colors: Vec<(lsp_types::Range, Hsla)>, /// Cached semantic tokens as absolute position ranges + theme token-type @@ -55,6 +66,7 @@ impl Default for Lsp { definition_provider: None, document_color_provider: None, semantic_tokens_provider: None, + on_open_location: None, document_colors: vec![], semantic_tokens: vec![], _hover_task: Task::ready(Ok(())), From e007d25d47b1701bf6fd40cd248627c01cd6fcc1 Mon Sep 17 00:00:00 2001 From: ylinwind Date: Tue, 14 Jul 2026 22:00:28 +0800 Subject: [PATCH 2/2] input: model show-document hook after LSP window/showDocument Rename on_open_location to on_show_document and pass lsp_types::ShowDocumentParams (uri, external, take_focus, selection) instead of a raw LocationLink, following the window/showDocument request from the LSP spec. The default in-document jump now selects target_selection_range per the spec, instead of target_range. Co-Authored-By: Claude Fable 5 --- crates/ui/src/input/lsp/definitions.rs | 29 ++++++++++++++++---------- crates/ui/src/input/lsp/mod.rs | 24 +++++++++++++-------- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/crates/ui/src/input/lsp/definitions.rs b/crates/ui/src/input/lsp/definitions.rs index 475eebbc27..f542b5cdfc 100644 --- a/crates/ui/src/input/lsp/definitions.rs +++ b/crates/ui/src/input/lsp/definitions.rs @@ -160,24 +160,31 @@ impl InputState { window: &mut Window, cx: &mut Context, ) { - // Give the host a chance to handle the location first, e.g. to open - // virtual/external documents (stdlib docs) in an app window. - if let Some(handler) = self.lsp.on_open_location.clone() { - if handler(location, window, cx) { + let external = location + .target_uri + .scheme() + .map(|s| s.as_str() == "https" || s.as_str() == "http") + == Some(true); + + // Give the host a chance to show the document first (window/showDocument), + // e.g. to open virtual/external documents (stdlib docs) in an app window. + if let Some(handler) = self.lsp.show_document.clone() { + let params = lsp_types::ShowDocumentParams { + uri: location.target_uri.clone(), + external: Some(external), + take_focus: Some(true), + selection: Some(location.target_selection_range), + }; + if handler(¶ms, window, cx) { return; } } - if location - .target_uri - .scheme() - .map(|s| s.as_str() == "https" || s.as_str() == "http") - == Some(true) - { + if external { cx.open_url(&location.target_uri.to_string()); } else { // Move to the location. - let target_range = location.target_range; + let target_range = location.target_selection_range; let start = self.text.position_to_offset(&target_range.start); let end = self.text.position_to_offset(&target_range.end); diff --git a/crates/ui/src/input/lsp/mod.rs b/crates/ui/src/input/lsp/mod.rs index 0c6d968d20..482771fe6b 100644 --- a/crates/ui/src/input/lsp/mod.rs +++ b/crates/ui/src/input/lsp/mod.rs @@ -19,13 +19,17 @@ pub use document_colors::*; pub use hover::*; pub use semantic_tokens::*; -/// Host hook to intercept opening an LSP location (Go to Definition). +/// Host hook to show a document when following an LSP location +/// (Go to Definition), modeled after the `window/showDocument` request. /// -/// Called before the built-in behavior. Return `true` if the host handled -/// the location (e.g. opened a docs window for a virtual/external URI); -/// return `false` to fall through to the default handling (http(s) URIs +/// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_showDocument +/// +/// Called before the built-in behavior. Return `true` if the host has shown +/// the document (e.g. opened a docs window for a virtual/external URI); +/// return `false` to fall through to the default handling (`external` URIs /// open in the browser, anything else jumps within the current document). -pub type OpenLocationHandler = Rc bool>; +pub type ShowDocumentHandler = + Rc bool>; /// LSP ServerCapabilities /// @@ -43,9 +47,11 @@ pub struct Lsp { pub document_color_provider: Option>, /// The range semantic tokens provider. pub semantic_tokens_provider: Option>, - /// Optional host hook to intercept Go to Definition locations - /// (see [`OpenLocationHandler`]). - pub on_open_location: Option, + /// Optional host hook to show documents for Go to Definition locations, + /// following the `window/showDocument` request (see [`ShowDocumentHandler`]). + /// + /// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_showDocument + pub show_document: Option, document_colors: Vec<(lsp_types::Range, Hsla)>, /// Cached semantic tokens as absolute position ranges + theme token-type @@ -66,7 +72,7 @@ impl Default for Lsp { definition_provider: None, document_color_provider: None, semantic_tokens_provider: None, - on_open_location: None, + show_document: None, document_colors: vec![], semantic_tokens: vec![], _hover_task: Task::ready(Ok(())),