diff --git a/crates/ide-host/src/ops/lsp.rs b/crates/ide-host/src/ops/lsp.rs index 535b1acd4..c4a14fa8c 100644 --- a/crates/ide-host/src/ops/lsp.rs +++ b/crates/ide-host/src/ops/lsp.rs @@ -677,7 +677,22 @@ pub async fn lsp_format( } pub async fn lsp_inlay_hints(host: &Host, window: &str, doc: DocId, range: Range) -> IdeResult> { - host.project(window)?.lsp.inlay_hints(doc, range).await + let lsp = host.project(window)?.lsp.clone(); + let Some((plugins, d)) = plugin_providers(host, window, doc, ProviderKind::InlayHints) else { + return lsp.inlay_hints(doc, range).await; + }; + let (result, hints) = + tokio::join!(lsp.inlay_hints(doc, range), plugin_call(move || plugins.provide_inlay_hints(&d, range))); + let hints = hints.unwrap_or_default(); + if hints.is_empty() { + return result; + } + let server = match result { + Ok(server) => server, + Err(e) if matches!(e.kind, ErrorKind::NotFound | ErrorKind::Unavailable) => Vec::new(), + Err(e) => return Err(e), + }; + Ok(crate::plugins::merge_inlay_hints(server, hints, range)) } pub async fn lsp_semantic_tokens(host: &Host, window: &str, doc: DocId) -> IdeResult> { diff --git a/crates/ide-host/src/plugins/inlay_hints.rs b/crates/ide-host/src/plugins/inlay_hints.rs new file mode 100644 index 000000000..b7636c92a --- /dev/null +++ b/crates/ide-host/src/plugins/inlay_hints.rs @@ -0,0 +1,94 @@ +use ide_core::Range; +use ide_lsp::types::{InlayHint, InlayKind}; +use ide_plugin::PluginInlayHint; + +pub(crate) fn merge(server: Vec, plugin: Vec, range: Range) -> Vec { + let inside = |h: &PluginInlayHint| { + let at = (h.position.line, h.position.col); + (range.start.line, range.start.col) <= at && at <= (range.end.line, range.end.col) + }; + let mut out = server; + out.extend(plugin.into_iter().filter(inside).map(|h| InlayHint { + position: h.position, + label: h.label, + kind: match h.kind.as_str() { + "type" => InlayKind::Type, + "parameter" => InlayKind::Parameter, + _ => InlayKind::Other, + }, + padding_left: h.padding_left, + padding_right: h.padding_right, + tooltip: h.tooltip, + parts: Vec::new(), + })); + out.sort_by_key(|h| (h.position.line, h.position.col)); + out +} + +#[cfg(test)] +mod tests { + use super::*; + use ide_core::Pos; + + fn plugin(line: u32, col: u32, label: &str, kind: &str) -> PluginInlayHint { + PluginInlayHint { + position: Pos::new(line, col), + label: label.into(), + kind: kind.into(), + tooltip: Some("from the plugin".into()), + padding_left: true, + padding_right: false, + } + } + + fn server(line: u32, col: u32, label: &str) -> InlayHint { + InlayHint { + position: Pos::new(line, col), + label: label.into(), + kind: InlayKind::Parameter, + padding_left: false, + padding_right: true, + tooltip: None, + parts: Vec::new(), + } + } + + fn summary(hints: &[InlayHint]) -> Vec<(u32, u32, String)> { + hints.iter().map(|h| (h.position.line, h.position.col, h.label.clone())).collect() + } + + #[test] + fn plugin_hints_join_the_servers_in_position_order() { + let range = Range::new(Pos::new(0, 0), Pos::new(10, 0)); + let merged = merge( + vec![server(2, 4, "name:"), server(5, 1, "id:")], + vec![plugin(3, 9, ": string", "type"), plugin(2, 1, "GET /orders", "other")], + range, + ); + assert_eq!( + summary(&merged), + [ + (2, 1, "GET /orders".to_owned()), + (2, 4, "name:".to_owned()), + (3, 9, ": string".to_owned()), + (5, 1, "id:".to_owned()) + ] + ); + let typed = merged.iter().find(|h| h.label == ": string").unwrap(); + assert!(matches!(typed.kind, InlayKind::Type)); + assert!(typed.padding_left && !typed.padding_right); + assert_eq!(typed.tooltip.as_deref(), Some("from the plugin")); + } + + #[test] + fn unknown_kinds_are_other_and_hints_outside_the_range_are_dropped() { + let range = Range::new(Pos::new(2, 5), Pos::new(4, 3)); + let merged = merge( + Vec::new(), + vec![plugin(2, 4, "before", "type"), plugin(3, 0, "in", "route"), plugin(4, 4, "after", "type")], + range, + ); + assert_eq!(summary(&merged), [(3, 0, "in".to_owned())]); + assert!(matches!(merged[0].kind, InlayKind::Other)); + } +} diff --git a/crates/ide-host/src/plugins/mod.rs b/crates/ide-host/src/plugins/mod.rs index be40ee1c5..8d66bf1e4 100644 --- a/crates/ide-host/src/plugins/mod.rs +++ b/crates/ide-host/src/plugins/mod.rs @@ -23,6 +23,7 @@ mod icon_patterns; mod icon_settings; mod icons; pub mod index_ops; +mod inlay_hints; mod install; pub mod intellij; mod market; @@ -44,6 +45,7 @@ pub(crate) use hover::merge as merge_hover; pub use icons::{ MAX_PLUGIN_ICON, map_asset as icon_theme_map_asset, plugin_icon, sanitize_svg, svg_prefix as icon_theme_svg_prefix, }; +pub(crate) use inlay_hints::merge as merge_inlay_hints; pub use market::{ INDEX_FILE as MARKET_INDEX_FILE, MarketPlugin, OFFICIAL_INDEX as OFFICIAL_MARKET_INDEX, OFFICIAL_PUBLISHER, read_url as read_market_url, resolve as resolve_market_href, diff --git a/crates/ide-host/src/plugins/providers.rs b/crates/ide-host/src/plugins/providers.rs index b262b709a..2703ca127 100644 --- a/crates/ide-host/src/plugins/providers.rs +++ b/crates/ide-host/src/plugins/providers.rs @@ -39,6 +39,7 @@ pub enum ProviderKind { LineMarkers, SemanticTokens, Hover, + InlayHints, } #[derive(Clone, Debug, Deserialize)] @@ -120,6 +121,9 @@ impl ExtensionPoint for ProvidersPoint { if d.kinds.contains(&ProviderKind::Hover) { pkg.manifest.needs_api("contributes.providers: hover", 3)?; } + if d.kinds.contains(&ProviderKind::InlayHints) { + pkg.manifest.needs_api("contributes.providers: inlay-hints", 3)?; + } for l in &d.languages { if l != "*" && (l.is_empty() @@ -169,7 +173,7 @@ use super::{LoadedPlugin, PluginHost, PluginStatus}; use ide_core::CancellationToken; use ide_plugin::{ ActorError, CallError, CodeAction, CompletionItem, InlineSuggestion, LineMarker, Location, Pending, PluginHover, - ProviderDocument, Runtime, SemanticToken, + PluginInlayHint, ProviderDocument, Runtime, SemanticToken, }; use std::sync::Weak; use std::time::{Duration, Instant}; @@ -357,6 +361,16 @@ impl PluginHost { .collect() } + pub fn provide_inlay_hints(&self, doc: &ProviderDocument, range: ide_core::Range) -> Vec { + let d = Arc::new(doc.clone()); + self.edit_calls(ProviderKind::InlayHints, &doc.language, move |rt, timeout| { + rt.provide_inlay_hints(&d, range, timeout) + }) + .into_iter() + .flat_map(|(_, hints)| hints) + .collect() + } + /// Ghost text from the first plugin that has some. Not bound by the /// edit-path soft deadline (it may need the network) but by `cancel`: /// the editor cancels a request as soon as the user types on. diff --git a/crates/ide-host/tests/plugin_inlay_hints.rs b/crates/ide-host/tests/plugin_inlay_hints.rs new file mode 100644 index 000000000..c7d7c239f --- /dev/null +++ b/crates/ide-host/tests/plugin_inlay_hints.rs @@ -0,0 +1,59 @@ +use ide_core::{Pos, Range}; +use ide_host::plugins::{Consent, PluginHost}; +use ide_plugin::{Limits, ProviderDocument}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use std::time::Duration; + +fn fixture_dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("../ide-plugin/tests/fixtures/inlay-hints") +} + +fn host(dir: &Path) -> PluginHost { + let limits = Limits { edit_soft: Duration::from_secs(5), edit_hard: Duration::from_secs(10), ..Limits::default() }; + PluginHost::new(dir.join("plugins"), Arc::new(|_: Option<&str>, _| {}), limits).unwrap() +} + +fn doc(dir: &Path, language: &str, text: &str) -> ProviderDocument { + ProviderDocument { path: dir.join("Order.php"), language: language.into(), version: 1, text: text.into() } +} + +const ORDER: &str = "user();\n$order->total();\n$order->user;\n"; + +fn all() -> Range { + Range::new(Pos::new(0, 0), Pos::new(100, 0)) +} + +#[test] +fn calls_of_declared_methods_get_a_type_hint() { + let tmp = tempfile::tempdir().unwrap(); + let host = host(tmp.path()); + host.install_from_dir(&fixture_dir(), &Consent::all()).unwrap(); + let hints = host.provide_inlay_hints(&doc(tmp.path(), "php", ORDER), all()); + let found: Vec<_> = + hints.iter().map(|h| (h.position.line, h.position.col, h.label.as_str(), h.kind.as_str())).collect(); + assert_eq!(found, [(5, 14, ": ?User", "type")]); + assert_eq!(hints[0].tooltip.as_deref(), Some("user() returns ?User")); + assert!(!hints[0].padding_left && !hints[0].padding_right); +} + +#[test] +fn only_declared_languages_are_asked() { + let tmp = tempfile::tempdir().unwrap(); + let host = host(tmp.path()); + host.install_from_dir(&fixture_dir(), &Consent::all()).unwrap(); + assert!(host.provide_inlay_hints(&doc(tmp.path(), "javascript", ORDER), all()).is_empty()); +} + +#[test] +fn the_kind_needs_plugin_api_1_3() { + let tmp = tempfile::tempdir().unwrap(); + let host = host(tmp.path()); + let pkg = tmp.path().join("old"); + std::fs::create_dir_all(pkg.join("dist")).unwrap(); + std::fs::copy(fixture_dir().join("dist/plugin.wasm"), pkg.join("dist/plugin.wasm")).unwrap(); + let manifest = std::fs::read_to_string(fixture_dir().join("plugin.toml")).unwrap(); + std::fs::write(pkg.join("plugin.toml"), manifest.replace("\"^1.3\"", "\"^1.0\"")).unwrap(); + let err = host.install_from_dir(&pkg, &Consent::all()).unwrap_err(); + assert!(err.message.contains("new in plugin API 1.3"), "{}", err.message); +} diff --git a/crates/ide-plugin/src/runtime.rs b/crates/ide-plugin/src/runtime.rs index c9c747402..4f46788b2 100644 --- a/crates/ide-plugin/src/runtime.rs +++ b/crates/ide-plugin/src/runtime.rs @@ -5,7 +5,7 @@ use crate::types::{ CodeAction, CommitDraft, CommitProblem, CompletionItem, InlineSuggestion, LineMarker, Location, PluginHover, - ProviderDocument, SemanticToken, + PluginInlayHint, ProviderDocument, SemanticToken, }; use crate::types::{ EventKind, HostError, HttpRequest, HttpResponse, Invocation, Limits, NotificationLevel, ProcessOutput, Prompt, @@ -16,8 +16,8 @@ use crate::types::{ ViewField, ViewListItem, ViewTable, ViewTreeItem, Viewport, }; use crate::wit_host::{ - bindings, commit_checks_bindings, details_bindings, hover_bindings, indexing_bindings, providers_bindings, - semantic_bindings, syntax_bindings, views_bindings, + bindings, commit_checks_bindings, details_bindings, hover_bindings, indexing_bindings, inlay_bindings, + providers_bindings, semantic_bindings, syntax_bindings, views_bindings, }; use ide_core::CancellationToken; use ide_core::ipc_path::to_ipc; @@ -39,6 +39,7 @@ use details_bindings::exports::openidea::plugin::detail_provider as wit_detail; use details_bindings::openidea::plugin::detail_types as wit_dt; use hover_bindings::exports::openidea::plugin::hover_provider as wit_hover; use indexing_bindings::exports::openidea::plugin::document_indexer as wit_index; +use inlay_bindings::exports::openidea::plugin::inlay_hints_provider as wit_inlay; use providers_bindings::exports::openidea::plugin::providers as wit_prov; use semantic_bindings::exports::openidea::plugin::semantic_tokens_provider as wit_sem; use syntax_bindings::exports::openidea::plugin::syntax_provider as wit_syntax; @@ -545,6 +546,7 @@ struct Exports { details: Option, hover: Option, commit_checks: Option, + inlay: Option, } /// Most results one syntax call may return (more are dropped). @@ -628,6 +630,7 @@ impl Runtime { let detail_indices = wit_detail::GuestIndices::new(&pre).ok(); let hover_indices = wit_hover::GuestIndices::new(&pre).ok(); let commit_check_indices = wit_cc::GuestIndices::new(&pre).ok(); + let inlay_indices = wit_inlay::GuestIndices::new(&pre).ok(); let instance = pre.instantiate(&mut store).map_err(api_hint)?; let plugin = plugin_indices.load(&mut store, &instance).map_err(api_hint)?; let syntax = match syntax_indices { @@ -662,9 +665,24 @@ impl Runtime { Some(indices) => Some(indices.load(&mut store, &instance).map_err(api_hint)?), None => None, }; + let inlay = match inlay_indices { + Some(indices) => Some(indices.load(&mut store, &instance).map_err(api_hint)?), + None => None, + }; Ok(Self { store, - exports: Exports { plugin, syntax, providers, views, indexer, semantic, details, hover, commit_checks }, + exports: Exports { + plugin, + syntax, + providers, + views, + indexer, + semantic, + details, + hover, + commit_checks, + inlay, + }, limits: setup.limits, version: setup.version, }) @@ -945,6 +963,40 @@ impl Runtime { .map(|h| PluginHover { markdown: clip(h.markdown, MAX_HOVER_BYTES), range: h.range.map(range_from_wit) })) } + pub fn provide_inlay_hints( + &mut self, + doc: &ProviderDocument, + range: ide_core::Range, + timeout: Duration, + ) -> Result, CallError> { + if self.exports.inlay.is_none() { + return Err(CallError::missing_export("inlay-hints-provider")); + } + let d = wit_inlay::Document { + path: to_ipc(&doc.path), + language: doc.language.clone(), + version: doc.version, + text: doc.text.clone(), + }; + let r = wit_types::Range { start: pos_to_wit(range.start), end: pos_to_wit(range.end) }; + let hints = self.call(Invocation::default(), timeout, |e, store| { + e.inlay.as_ref().expect("checked").call_inlay_hints(store, &d, r) + })?; + Ok(hints + .into_iter() + .filter(|h| !h.label.trim().is_empty()) + .take(MAX_PROVIDER_ITEMS) + .map(|h| PluginInlayHint { + position: ide_core::Pos::new(h.position.line, h.position.character), + label: clip(h.label, 256), + kind: clip(h.kind, 32), + tooltip: h.tooltip.map(|t| clip(t, 1024)), + padding_left: h.padding_left, + padding_right: h.padding_right, + }) + .collect()) + } + pub fn activate(&mut self) -> Result<(), CallError> { let data = self.store.data(); let ctx = wit_types::Context { diff --git a/crates/ide-plugin/src/types.rs b/crates/ide-plugin/src/types.rs index 6239d3822..1156e0779 100644 --- a/crates/ide-plugin/src/types.rs +++ b/crates/ide-plugin/src/types.rs @@ -466,6 +466,16 @@ pub struct PluginHover { pub range: Option, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PluginInlayHint { + pub position: ide_core::Pos, + pub label: String, + pub kind: String, + pub tooltip: Option, + pub padding_left: bool, + pub padding_right: bool, +} + // ---- declarative views (wit/views.wit) --------------------------------------- #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Type)] diff --git a/crates/ide-plugin/src/wit_host.rs b/crates/ide-plugin/src/wit_host.rs index 88bd4da8f..088157272 100644 --- a/crates/ide-plugin/src/wit_host.rs +++ b/crates/ide-plugin/src/wit_host.rs @@ -968,3 +968,10 @@ pub(crate) mod hover_bindings { with: { "openidea:plugin/types": super::bindings::openidea::plugin::types }, }); } + +pub(crate) mod inlay_bindings { + wasmtime::component::bindgen!({ + world: "inlay-hints", path: "../../wit", + with: { "openidea:plugin/types": super::bindings::openidea::plugin::types }, + }); +} diff --git a/crates/ide-plugin/tests/api_compat.rs b/crates/ide-plugin/tests/api_compat.rs index 40ec97c09..4ed1461aa 100644 --- a/crates/ide-plugin/tests/api_compat.rs +++ b/crates/ide-plugin/tests/api_compat.rs @@ -30,6 +30,7 @@ fn components() -> Vec { repo().join("crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm"), repo().join("crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm"), repo().join("crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm"), + repo().join("crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm"), ]; let official = repo().join("plugins/official"); let mut dirs: Vec<_> = std::fs::read_dir(&official).unwrap().map(|e| e.unwrap().path()).collect(); diff --git a/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm index 4b6b98271..8f307470c 100644 Binary files a/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm.sources index 8c0ea93f8..163d705e8 100644 --- a/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm.sources @@ -1 +1 @@ -09a64c18ef137348f70121c088419040ee606089d9f27b0dbe63256d5de49cd6 +9cdb9b328e2b72c2f8849548b1d5bc119044acb4136dac614319138db2e4890d diff --git a/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm index 749fe1995..ba88bd79d 100644 Binary files a/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm.sources index bcdc40579..b688f54a2 100644 --- a/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm.sources @@ -1 +1 @@ -8eba458c25d7722ffc1d396812bd83114a67c1176b9dd6fa3bfbbf02c0ca0976 +32634dd8c1172c6980cd1c4f9345fb2ed7b2e5cbacfd061bcefaa7fa9aad162e diff --git a/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm index 970d5ea47..efcad8abe 100644 Binary files a/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources index 5195b70b7..b674f0515 100644 --- a/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources @@ -1 +1 @@ -5f7112055543becca6fc9ad9a33850c355efe38b3ab019ecb4fb26346f3ac9b5 +b7b63faf536d558cafc375a17765823015863e47d897e2128d453fbc8975e19c diff --git a/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm index 6b1c84993..ed7a7ecb6 100644 Binary files a/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm.sources index 3d6b36a1e..40d6ceee8 100644 --- a/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm.sources @@ -1 +1 @@ -1f571177a22c2f07055b38385c0e5f44c063c520b9e1d7be3d528e4cfb3b8b43 +809180a1c4ba29988af7b1ead1c31c1a3018fe3a1c266930ba588f34de1d3319 diff --git a/crates/ide-plugin/tests/fixtures/inlay-hints/Cargo.lock b/crates/ide-plugin/tests/fixtures/inlay-hints/Cargo.lock new file mode 100644 index 000000000..7e9f07917 --- /dev/null +++ b/crates/ide-plugin/tests/fixtures/inlay-hints/Cargo.lock @@ -0,0 +1,333 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "log" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c" + +[[package]] +name = "macro-string" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e7b3a5bbb2f63aaa223a671ea1bd0e7bc16bd30ce387324cf63995deaddbbd2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "openidea-plugin" +version = "0.1.0" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "openidea-test-inlay-hints" +version = "0.1.0" +dependencies = [ + "openidea-plugin", +] + +[[package]] +name = "prettyplease" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "wasm-encoder" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4495a6f85dd94c411c749303efcbc5defade0b0cbdbc0541495241ab917a9b5a" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c86ff6668190d9fb70372b306b4f9fe8930f0e2f57ce72e4922bf2f117900949" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "974d643b125b272607e2fff8012ea14a8328acd7621e4bafa7afb32500052b1b" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b85a987ab61d365382d25c7d47e26edd48941b3bf6e3fbf76eff918bc71dab06" +dependencies = [ + "bitflags", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e122e390603abfacd548027016cd0113a6e2bf2f8ce6bdd3658b013456ea8" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e26e13b9d972026778d2ffb7985736de116026fdc8d9464f966e12df2eb240" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a008a0d6b4840677326beb4edbeb210a2f1b7d0183fb7cc33f466b90fd7fb6" +dependencies = [ + "anyhow", + "macro-string", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078def49fdb49cab405c3006e05793b3fdaf7069126ba6b9c97d01ec8c98fe4c" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.262.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f0dc051466ffe46787721fca3d088536f158ae400be044f1b1c386ba9397ad" +dependencies = [ + "anyhow", + "hashbrown", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-ident", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/crates/ide-plugin/tests/fixtures/inlay-hints/Cargo.toml b/crates/ide-plugin/tests/fixtures/inlay-hints/Cargo.toml new file mode 100644 index 000000000..866631f6f --- /dev/null +++ b/crates/ide-plugin/tests/fixtures/inlay-hints/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "openidea-test-inlay-hints" +version = "0.1.0" +edition = "2024" +publish = false + +[workspace] + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +openidea-plugin = { path = "../../../../../sdk/rust/openidea-plugin" } + +[profile.release] +opt-level = "s" +lto = true +strip = true +codegen-units = 1 +panic = "abort" diff --git a/crates/ide-plugin/tests/fixtures/inlay-hints/README.md b/crates/ide-plugin/tests/fixtures/inlay-hints/README.md new file mode 100644 index 000000000..ba580f9a2 --- /dev/null +++ b/crates/ide-plugin/tests/fixtures/inlay-hints/README.md @@ -0,0 +1,7 @@ +# Inlay hints test fixture + +After every `->name()` call in a PHP document that declares +`function name(): Type`, shows `: Type` as a type hint. + +Used by `cargo test -p ide-host --test plugin_inlay_hints` to exercise the +optional `inlay-hints-provider` export. It is not a marketplace plugin. diff --git a/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm new file mode 100644 index 000000000..6be609a4e Binary files /dev/null and b/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources new file mode 100644 index 000000000..f2dbd86d5 --- /dev/null +++ b/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources @@ -0,0 +1 @@ +6fd9e638afa22170ef2e7a6bd7351bd5e0200d6726cf5d5835be7c5c7cfa19dc diff --git a/crates/ide-plugin/tests/fixtures/inlay-hints/plugin.toml b/crates/ide-plugin/tests/fixtures/inlay-hints/plugin.toml new file mode 100644 index 000000000..bb1c7c3f8 --- /dev/null +++ b/crates/ide-plugin/tests/fixtures/inlay-hints/plugin.toml @@ -0,0 +1,9 @@ +id = "test.inlay-hints" +name = "Inlay hints fixture" +version = "0.1.0" +engines.openidea = "^1.3" +wasm = "dist/plugin.wasm" + +[[contributes.providers]] +kinds = ["inlay-hints"] +languages = ["php"] diff --git a/crates/ide-plugin/tests/fixtures/inlay-hints/src/lib.rs b/crates/ide-plugin/tests/fixtures/inlay-hints/src/lib.rs new file mode 100644 index 000000000..603f6d112 --- /dev/null +++ b/crates/ide-plugin/tests/fixtures/inlay-hints/src/lib.rs @@ -0,0 +1,47 @@ +use openidea_plugin::inlay_hints::{Document, InlayHint, InlayHints, InlayKind}; +use openidea_plugin::{Error, Invocation, Plugin, Range, register_plugin, text}; + +#[derive(Default)] +struct Fixture; + +impl Plugin for Fixture { + fn run_command(&mut self, id: &str, _: &Invocation, _: Option<&str>) -> Result, Error> { + Err(Error::NotFound(id.into())) + } +} + +impl InlayHints for Fixture { + fn inlay_hints(&mut self, doc: &Document, _range: Range) -> Vec { + let text = doc.text.as_str(); + let mut out = Vec::new(); + let mut rest = 0; + while let Some(i) = text[rest..].find("->") { + let start = rest + i + 2; + let end = text[start..] + .find(|c: char| !(c.is_ascii_alphanumeric() || c == '_')) + .map_or(text.len(), |n| start + n); + rest = end; + if end == start || !text[end..].starts_with("()") { + continue; + } + let name = &text[start..end]; + let declared = format!("function {name}(): "); + let Some(at) = text.find(&declared) else { continue }; + let ty: String = text[at + declared.len()..] + .chars() + .take_while(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '\\' | '?')) + .collect(); + if ty.is_empty() { + continue; + } + out.push( + InlayHint::new(text::position(text, end + 2), format!(": {ty}")) + .kind(InlayKind::Type) + .tooltip(format!("{name}() returns {ty}")), + ); + } + out + } +} + +register_plugin!(Fixture, inlay_hints); diff --git a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm index db4a7ca32..fa2d0592e 100644 Binary files a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources index 5b84fff77..371992907 100644 --- a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources @@ -1 +1 @@ -165f1dc7ebf47b3910fd8e23eb72cecfe47156c3498d2b06f5d6c39ecd299e2a +81a1c6a7d78cf6ca6e463ffff6a4e243d668230a8d802028262ed5fc752f2f27 diff --git a/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources index 26a06522f..30108e4f2 100644 --- a/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources @@ -1 +1 @@ -5be61cbf46d28a7d06c73fb2562e81f3d1a86e16c5c6389d402cbeaf703e331a +c3b3e3c96d62106398299a6704e6a39fb18cfa734be877d1137f005d16553b4b diff --git a/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm index 6d54047b7..e85457d1d 100644 Binary files a/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources index 266382c9e..03d4d1ef9 100644 --- a/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources @@ -1 +1 @@ -81db471a9be4aefb97d21c3937bc0afd637114cc2703fa2cc815b0ae61608467 +fac2994442eb96b09dea926546d2bacde333615392bc59fd70df73999ae84f62 diff --git a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm index 21b4a80e6..36c158c77 100644 Binary files a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources index 046bd2fbf..3af557be1 100644 --- a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources @@ -1 +1 @@ -cbb5a54e287ef3ed11391985c286e5d4e24646f2fd84b8b969fe825bbd44c350 +2a97235992302d0dfda55a1774883c046fe8aed6353b0f047aafa8bfe0155fa3 diff --git a/plugins/README.md b/plugins/README.md index 012e68d73..47381a4ad 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -34,7 +34,7 @@ plugins/ ├─ regex-tester/ # html view talking to its code, settings, the editor's selection └─ license-header/ # file-opened events, quick fix, project scan in a task sdk/rust/openidea-plugin/ # the Rust SDK (host API, Plugin trait, mock host) -crates/ide-plugin/tests/fixtures/ # test plugins: hello, jobs, language-toy, misbehave, semantic-tokens, hover, commit +crates/ide-plugin/tests/fixtures/ # test plugins: hello, jobs, language-toy, misbehave, semantic-tokens, hover, commit, inlay-hints ``` The plugin API is defined in [`wit/`](../wit/) (`openidea:plugin@1.3.0`, world @@ -448,7 +448,7 @@ declaration matches the file's language are called (and loaded): ```toml [[contributes.providers]] -kinds = ["definition", "line-markers", "completion"] # also "inline-completion", "code-actions", "semantic-tokens", "hover" +kinds = ["definition", "line-markers", "completion"] # also "inline-completion", "code-actions", "semantic-tokens", "hover", "inlay-hints" languages = ["java", "xml"] # editor language ids; "*" for any ``` @@ -523,6 +523,27 @@ edit-path budget, and shows the server's hover first, then each plugin's below a rule. `range`, when given, is the text the hover describes (used when the server names none). At most 64 KiB of Markdown per plugin. +#### Inlay hints + +The `inlay-hints` kind (API 1.3) shows short labels inside the code of +documents of the declared languages, such as a type after a call. Register +with `register_plugin!(MyPlugin, inlay_hints)` and implement +`openidea_plugin::inlay_hints::InlayHints`: + +```rust +impl InlayHints for MyPlugin { + fn inlay_hints(&mut self, doc: &Document, range: Range) -> Vec { + vec![InlayHint::new(range.start, ": string").kind(InlayKind::Type).tooltip("returns string")] + } +} +``` + +`range` is the visible part of the document; hints outside it are dropped. A +hint's kind (`type`, `parameter` or `other`) decides which Inlay Hints setting +shows it. The host asks the language server and the plugins together, under +the edit-path budget, and shows both; the editor asks again after edits and +scrolling. Labels are cut at 256 bytes and tooltips at 1 KiB. + ### Network and secrets `host::fetch` makes an HTTPS request (plain HTTP only to `localhost`) to a host diff --git a/plugins/official/ai-completion/dist/plugin.wasm b/plugins/official/ai-completion/dist/plugin.wasm index 022900fe1..191a729ce 100644 Binary files a/plugins/official/ai-completion/dist/plugin.wasm and b/plugins/official/ai-completion/dist/plugin.wasm differ diff --git a/plugins/official/ai-completion/dist/plugin.wasm.sources b/plugins/official/ai-completion/dist/plugin.wasm.sources index 9699c9978..e14bd9133 100644 --- a/plugins/official/ai-completion/dist/plugin.wasm.sources +++ b/plugins/official/ai-completion/dist/plugin.wasm.sources @@ -1 +1 @@ -2e34ee9cb774344d4dedf1ec077d776a051abd08e435f6df3dca6c7d12fa94fb +5de6d0c1cb0ee1a92d36b57ec18590f03829b2c29cc1c306be453931c528c692 diff --git a/plugins/official/checkstyle/dist/plugin.wasm b/plugins/official/checkstyle/dist/plugin.wasm index 226a8d384..5d0d07fff 100644 Binary files a/plugins/official/checkstyle/dist/plugin.wasm and b/plugins/official/checkstyle/dist/plugin.wasm differ diff --git a/plugins/official/checkstyle/dist/plugin.wasm.sources b/plugins/official/checkstyle/dist/plugin.wasm.sources index 1c67258b2..d18c3eb6f 100644 --- a/plugins/official/checkstyle/dist/plugin.wasm.sources +++ b/plugins/official/checkstyle/dist/plugin.wasm.sources @@ -1 +1 @@ -8ee84d6a4a835ec7104877fa105e593b47e8091339b3419690b018f3e8877b94 +9a89ec93d158e460998cf472837d7e0e8aa76d5441cb1dd24c3184bd814a29c1 diff --git a/plugins/official/csv-editor/dist/plugin.wasm b/plugins/official/csv-editor/dist/plugin.wasm index 9543406a8..4f93512a0 100644 Binary files a/plugins/official/csv-editor/dist/plugin.wasm and b/plugins/official/csv-editor/dist/plugin.wasm differ diff --git a/plugins/official/csv-editor/dist/plugin.wasm.sources b/plugins/official/csv-editor/dist/plugin.wasm.sources index a99cce3f5..da4a2a37d 100644 --- a/plugins/official/csv-editor/dist/plugin.wasm.sources +++ b/plugins/official/csv-editor/dist/plugin.wasm.sources @@ -1 +1 @@ -2af4ae2589bebf9703afa6b7b24bc073334344e6836b0ea29aab259338b38762 +652557045106ab5bd1126cb8e05965d030c4aa28c01ede31be3fe285aab98a67 diff --git a/plugins/official/git-toolbox/dist/plugin.wasm b/plugins/official/git-toolbox/dist/plugin.wasm index 806daedea..b238f5dd6 100644 Binary files a/plugins/official/git-toolbox/dist/plugin.wasm and b/plugins/official/git-toolbox/dist/plugin.wasm differ diff --git a/plugins/official/git-toolbox/dist/plugin.wasm.sources b/plugins/official/git-toolbox/dist/plugin.wasm.sources index ae55ff856..29ed91fd8 100644 --- a/plugins/official/git-toolbox/dist/plugin.wasm.sources +++ b/plugins/official/git-toolbox/dist/plugin.wasm.sources @@ -1 +1 @@ -a7d656941097113fdde3e64a07446d1780f045702a935a5e5153ac1f540d6b06 +7c8c97e02f754676ff512d10f454a7451468e2a98c2e992e790096215dd28658 diff --git a/plugins/official/graphviz/dist/plugin.wasm b/plugins/official/graphviz/dist/plugin.wasm index a239eef2e..9b5b24cb6 100644 Binary files a/plugins/official/graphviz/dist/plugin.wasm and b/plugins/official/graphviz/dist/plugin.wasm differ diff --git a/plugins/official/graphviz/dist/plugin.wasm.sources b/plugins/official/graphviz/dist/plugin.wasm.sources index ee6199398..fad8aa80e 100644 --- a/plugins/official/graphviz/dist/plugin.wasm.sources +++ b/plugins/official/graphviz/dist/plugin.wasm.sources @@ -1 +1 @@ -3f6549848f81cc999a42ba0fa04e0efb43640e09a67ab28d799d4f79832f6178 +1b6e3f30cc49d9d67d95f7655cbe337c5f8465c6ef7b8709cfab4734c3c40fdf diff --git a/plugins/official/key-promoter/dist/plugin.wasm b/plugins/official/key-promoter/dist/plugin.wasm index 8400b47a8..16096aafa 100644 Binary files a/plugins/official/key-promoter/dist/plugin.wasm and b/plugins/official/key-promoter/dist/plugin.wasm differ diff --git a/plugins/official/key-promoter/dist/plugin.wasm.sources b/plugins/official/key-promoter/dist/plugin.wasm.sources index a71ed00b0..0686d1aa4 100644 --- a/plugins/official/key-promoter/dist/plugin.wasm.sources +++ b/plugins/official/key-promoter/dist/plugin.wasm.sources @@ -1 +1 @@ -31ff37106dc0829e226de01b5148f6ee67b1d776acd6def4729edf4c63946219 +3225880631e679780c3c7eb09683b598bb3803fcf7be7b6940369eada633c48f diff --git a/plugins/official/license-header/dist/plugin.wasm b/plugins/official/license-header/dist/plugin.wasm index 6249ee735..d4729e4a7 100644 Binary files a/plugins/official/license-header/dist/plugin.wasm and b/plugins/official/license-header/dist/plugin.wasm differ diff --git a/plugins/official/license-header/dist/plugin.wasm.sources b/plugins/official/license-header/dist/plugin.wasm.sources index 737b70078..e9fd184a4 100644 --- a/plugins/official/license-header/dist/plugin.wasm.sources +++ b/plugins/official/license-header/dist/plugin.wasm.sources @@ -1 +1 @@ -eb4d09a79172cb46857c7bb3e50e7fbd6f4b6be379bb1c79a41ea23a5643f875 +a59623542b4df024f5a85bc2cd0e046189044a0c292c793384fdb5465e8eec48 diff --git a/plugins/official/mybatis/dist/plugin.wasm b/plugins/official/mybatis/dist/plugin.wasm index 92a8b77a3..736d37b33 100644 Binary files a/plugins/official/mybatis/dist/plugin.wasm and b/plugins/official/mybatis/dist/plugin.wasm differ diff --git a/plugins/official/mybatis/dist/plugin.wasm.sources b/plugins/official/mybatis/dist/plugin.wasm.sources index d3ef9ff4e..ded07295b 100644 --- a/plugins/official/mybatis/dist/plugin.wasm.sources +++ b/plugins/official/mybatis/dist/plugin.wasm.sources @@ -1 +1 @@ -4f2e5d5c877554b111bb454603d002e50bbafb3dca3cca5768c052fbc48f73ab +f230ae32c2d5ab469f23c6851fc7830a6ecce7538c7d435be39b5bbebe38a64f diff --git a/plugins/official/regex-tester/dist/plugin.wasm b/plugins/official/regex-tester/dist/plugin.wasm index a493e8cf3..6c56e4a05 100644 Binary files a/plugins/official/regex-tester/dist/plugin.wasm and b/plugins/official/regex-tester/dist/plugin.wasm differ diff --git a/plugins/official/regex-tester/dist/plugin.wasm.sources b/plugins/official/regex-tester/dist/plugin.wasm.sources index 3a30df73a..014dda08e 100644 --- a/plugins/official/regex-tester/dist/plugin.wasm.sources +++ b/plugins/official/regex-tester/dist/plugin.wasm.sources @@ -1 +1 @@ -0cae1d9dd2075b1a107d894854f0d01ab81331b67793f2ad972835a8ab34d3a0 +63f52f8cf8a90affc751ac7e19352a89f0726fc7c8dbbb303172ebd42e23af8c diff --git a/plugins/official/solidity/dist/plugin.wasm b/plugins/official/solidity/dist/plugin.wasm index af2f58e70..de7498e01 100644 Binary files a/plugins/official/solidity/dist/plugin.wasm and b/plugins/official/solidity/dist/plugin.wasm differ diff --git a/plugins/official/solidity/dist/plugin.wasm.sources b/plugins/official/solidity/dist/plugin.wasm.sources index b75a15c60..3d6de62b0 100644 --- a/plugins/official/solidity/dist/plugin.wasm.sources +++ b/plugins/official/solidity/dist/plugin.wasm.sources @@ -1 +1 @@ -13e34d174abf5187bf7c191736974305afb0af257fc103eec96fe8fa8edb8c88 +178862630cf39bca56f5d54cd135dcac4a1ab74a3e10356163d00e5a718ae728 diff --git a/plugins/official/string-tools/dist/plugin.wasm b/plugins/official/string-tools/dist/plugin.wasm index 98c358652..e853e15dc 100644 Binary files a/plugins/official/string-tools/dist/plugin.wasm and b/plugins/official/string-tools/dist/plugin.wasm differ diff --git a/plugins/official/string-tools/dist/plugin.wasm.sources b/plugins/official/string-tools/dist/plugin.wasm.sources index 5967abb8d..0b4279f8f 100644 --- a/plugins/official/string-tools/dist/plugin.wasm.sources +++ b/plugins/official/string-tools/dist/plugin.wasm.sources @@ -1 +1 @@ -7e010175c19a159a22c42a76ca07ebc663a27ce9143c21e742ca99435a538f83 +a6c188aed4fc57cd9955d6b47ef715c52dc3f8c7b69f7d18ea71b3932888710f diff --git a/sdk/rust/openidea-plugin/src/inlay_hints.rs b/sdk/rust/openidea-plugin/src/inlay_hints.rs new file mode 100644 index 000000000..c9febbf44 --- /dev/null +++ b/sdk/rust/openidea-plugin/src/inlay_hints.rs @@ -0,0 +1,84 @@ +use crate::types::{Position, Range}; + +pub use crate::providers::Document; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum InlayKind { + Type, + Parameter, + Other, +} + +impl InlayKind { + pub fn as_str(self) -> &'static str { + match self { + Self::Type => "type", + Self::Parameter => "parameter", + Self::Other => "other", + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct InlayHint { + pub position: Position, + pub label: String, + pub kind: InlayKind, + pub tooltip: Option, + pub padding_left: bool, + pub padding_right: bool, +} + +impl InlayHint { + pub fn new(position: Position, label: impl Into) -> Self { + Self { + position, + label: label.into(), + kind: InlayKind::Other, + tooltip: None, + padding_left: false, + padding_right: false, + } + } + + pub fn kind(mut self, kind: InlayKind) -> Self { + self.kind = kind; + self + } + + pub fn tooltip(mut self, tooltip: impl Into) -> Self { + self.tooltip = Some(tooltip.into()); + self + } + + pub fn padding_left(mut self) -> Self { + self.padding_left = true; + self + } + + pub fn padding_right(mut self) -> Self { + self.padding_right = true; + self + } +} + +pub trait InlayHints { + fn inlay_hints(&mut self, doc: &Document, range: Range) -> Vec; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_hint_is_built_step_by_step() { + let at = Position { line: 3, character: 14 }; + let hint = InlayHint::new(at, "GET /orders").kind(InlayKind::Type).tooltip("orders.index").padding_left(); + assert_eq!(hint.position, at); + assert_eq!(hint.kind.as_str(), "type"); + assert_eq!(hint.tooltip.as_deref(), Some("orders.index")); + assert!(hint.padding_left && !hint.padding_right); + assert_eq!(InlayHint::new(at, "x").kind, InlayKind::Other); + } +} diff --git a/sdk/rust/openidea-plugin/src/lib.rs b/sdk/rust/openidea-plugin/src/lib.rs index 15647e5ff..3af96e16c 100644 --- a/sdk/rust/openidea-plugin/src/lib.rs +++ b/sdk/rust/openidea-plugin/src/lib.rs @@ -44,6 +44,7 @@ pub mod details; pub mod host; pub mod hover; pub mod indexing; +pub mod inlay_hints; #[cfg(not(target_arch = "wasm32"))] pub mod mock; pub mod providers; @@ -185,6 +186,17 @@ pub mod commit_checks_bindings { }); } +#[cfg(target_arch = "wasm32")] +#[doc(hidden)] +pub mod inlay_bindings { + wit_bindgen::generate!({ + world: "inlay-hints", path: "wit", pub_export_macro: true, + export_macro_name: "__export_inlay_hints_world", + default_bindings_module: "openidea_plugin::inlay_bindings", + with: { "openidea:plugin/types@1.3.0": crate::bindings::openidea::plugin::types }, + }); +} + /// The plugin API version this SDK targets. pub const API_VERSION: &str = "1.3"; @@ -251,6 +263,9 @@ macro_rules! __optional_exports { (commit_checks, $plugin:ty, $component:ident) => { $crate::__commit_checks_exports!($plugin, $component); }; + (inlay_hints, $plugin:ty, $component:ident) => { + $crate::__inlay_hints_exports!($plugin, $component); + }; (syntax, $plugin:ty, $component:ident) => { impl $crate::syntax_bindings::exports::openidea::plugin::syntax_provider::Guest for $component { fn highlights( @@ -457,6 +472,22 @@ macro_rules! __hover_exports { }; } +#[doc(hidden)] +#[macro_export] +macro_rules! __inlay_hints_exports { + ($plugin:ty, $component:ident) => { + impl $crate::inlay_bindings::exports::openidea::plugin::inlay_hints_provider::Guest for $component { + fn inlay_hints( + doc: $crate::inlay_bindings::exports::openidea::plugin::inlay_hints_provider::Document, + range: $crate::bindings::openidea::plugin::types::Range, + ) -> ::std::vec::Vec<$crate::inlay_bindings::exports::openidea::plugin::inlay_hints_provider::InlayHint> { + $crate::__rt::inlay_hints::<$plugin>(doc, range) + } + } + $crate::inlay_bindings::__export_inlay_hints_world!($component with_types_in $crate::inlay_bindings); + }; +} + #[doc(hidden)] #[macro_export] macro_rules! __plugin_exports { @@ -722,6 +753,25 @@ pub mod __rt { .map(|h| w::HoverContent { markdown: h.markdown, range: h.range.map(crate::host::__range_to) }) } + pub fn inlay_hints( + d: crate::inlay_bindings::exports::openidea::plugin::inlay_hints_provider::Document, + range: b::types::Range, + ) -> Vec { + use crate::inlay_bindings::exports::openidea::plugin::inlay_hints_provider as w; + let doc = crate::providers::Document { path: d.path, language: d.language, version: d.version, text: d.text }; + with::(|p| p.inlay_hints(&doc, crate::host::__range_from(range))) + .into_iter() + .map(|h| w::InlayHint { + position: b::types::Position { line: h.position.line, character: h.position.character }, + label: h.label, + kind: h.kind.as_str().into(), + tooltip: h.tooltip, + padding_left: h.padding_left, + padding_right: h.padding_right, + }) + .collect() + } + // ---- views ---- use crate::views as v; diff --git a/sdk/rust/openidea-plugin/wit/inlay.wit b/sdk/rust/openidea-plugin/wit/inlay.wit new file mode 100644 index 000000000..ab00b0c9b --- /dev/null +++ b/sdk/rust/openidea-plugin/wit/inlay.wit @@ -0,0 +1,39 @@ +package openidea:plugin@1.3.0; + +/// Optional export (world `plugin-inlay-hints`): inline hints in documents of +/// the languages declared with the `inlay-hints` provider kind, including +/// languages the plugin doesn't contribute. They are shown next to the +/// language server's hints. +interface inlay-hints-provider { + use types.{position, range}; + + /// The document the hints are for (the editor's text while it is open). + record document { + path: string, + language: string, + version: u64, + text: string, + } + + /// A hint shown at `position`, before the character there. + record inlay-hint { + /// UTF-16 position (0-based line and column). + position: position, + /// The text shown, e.g. `GET /orders` or `: string`. + label: string, + /// `type` or `parameter` follow the Inlay Hints settings of that kind; + /// anything else counts as `other`. + kind: string, + /// Shown when the pointer rests on the hint. + tooltip: option, + /// A space before the label. + padding-left: bool, + /// A space after the label. + padding-right: bool, + } + + /// The hints within `range` (the visible part of the document), under the + /// edit-path budget. Hints outside `range` are dropped; at most 10,000 per + /// call. + inlay-hints: func(doc: document, range: range) -> list; +} diff --git a/sdk/rust/openidea-plugin/wit/plugin.wit b/sdk/rust/openidea-plugin/wit/plugin.wit index b01f7a96f..19c6dc9e1 100644 --- a/sdk/rust/openidea-plugin/wit/plugin.wit +++ b/sdk/rust/openidea-plugin/wit/plugin.wit @@ -67,6 +67,12 @@ world plugin-hover { export hover-provider; } +/// A plugin with inlay hints (`inlay-hints` under `[[contributes.providers]]`). +world plugin-inlay-hints { + include plugin; + export inlay-hints-provider; +} + /// A plugin whose views show in the Services tool window /// (`location = "services"`): their nodes' detail pane. world plugin-details { @@ -109,6 +115,11 @@ world hover { export hover-provider; } +/// Only the `inlay-hints-provider` export, generated next to `plugin` like `syntax`. +world inlay-hints { + export inlay-hints-provider; +} + /// Only the `detail-provider` export, generated next to `plugin` like `syntax`. world details { export detail-provider; diff --git a/wit/inlay.wit b/wit/inlay.wit new file mode 100644 index 000000000..ab00b0c9b --- /dev/null +++ b/wit/inlay.wit @@ -0,0 +1,39 @@ +package openidea:plugin@1.3.0; + +/// Optional export (world `plugin-inlay-hints`): inline hints in documents of +/// the languages declared with the `inlay-hints` provider kind, including +/// languages the plugin doesn't contribute. They are shown next to the +/// language server's hints. +interface inlay-hints-provider { + use types.{position, range}; + + /// The document the hints are for (the editor's text while it is open). + record document { + path: string, + language: string, + version: u64, + text: string, + } + + /// A hint shown at `position`, before the character there. + record inlay-hint { + /// UTF-16 position (0-based line and column). + position: position, + /// The text shown, e.g. `GET /orders` or `: string`. + label: string, + /// `type` or `parameter` follow the Inlay Hints settings of that kind; + /// anything else counts as `other`. + kind: string, + /// Shown when the pointer rests on the hint. + tooltip: option, + /// A space before the label. + padding-left: bool, + /// A space after the label. + padding-right: bool, + } + + /// The hints within `range` (the visible part of the document), under the + /// edit-path budget. Hints outside `range` are dropped; at most 10,000 per + /// call. + inlay-hints: func(doc: document, range: range) -> list; +} diff --git a/wit/plugin.wit b/wit/plugin.wit index b01f7a96f..19c6dc9e1 100644 --- a/wit/plugin.wit +++ b/wit/plugin.wit @@ -67,6 +67,12 @@ world plugin-hover { export hover-provider; } +/// A plugin with inlay hints (`inlay-hints` under `[[contributes.providers]]`). +world plugin-inlay-hints { + include plugin; + export inlay-hints-provider; +} + /// A plugin whose views show in the Services tool window /// (`location = "services"`): their nodes' detail pane. world plugin-details { @@ -109,6 +115,11 @@ world hover { export hover-provider; } +/// Only the `inlay-hints-provider` export, generated next to `plugin` like `syntax`. +world inlay-hints { + export inlay-hints-provider; +} + /// Only the `detail-provider` export, generated next to `plugin` like `syntax`. world details { export detail-provider;