Skip to content

Commit 9c3f6d6

Browse files
committed
Add support for rust-analyzer chaining hints (autozimu#1108)
1 parent 1930289 commit 9c3f6d6

File tree

4 files changed

+210
-22
lines changed

4 files changed

+210
-22
lines changed

src/extensions/mod.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,43 @@ pub mod clangd;
22
pub mod gopls;
33
pub mod java;
44
pub mod rust_analyzer;
5+
6+
use crate::language_client::LanguageClient;
7+
use anyhow::Result;
8+
9+
impl LanguageClient {
10+
pub fn text_document_inlay_hints(&self, language_id: &str, filename: &str) -> Result<()> {
11+
if !self.extensions_enabled(language_id)? {
12+
return Ok(());
13+
}
14+
15+
let server_name = self.get_state(|state| match state.capabilities.get(language_id) {
16+
Some(c) => c
17+
.server_info
18+
.as_ref()
19+
.map(|info| info.name.clone())
20+
.unwrap_or_default(),
21+
None => String::new(),
22+
})?;
23+
24+
let hints = match server_name.as_str() {
25+
rust_analyzer::SERVER_NAME => self.rust_analyzer_inlay_hints(filename)?,
26+
_ => return Ok(()),
27+
};
28+
29+
self.update_state(|state| {
30+
state.inlay_hints.insert(filename.to_string(), hints);
31+
Ok(())
32+
})?;
33+
34+
Ok(())
35+
}
36+
37+
pub fn extensions_enabled(&self, filetype: &str) -> Result<bool> {
38+
let result = self.get_config(|c| match &c.enable_extensions {
39+
Some(extensions) => extensions.get(filetype).cloned().unwrap_or(true),
40+
None => true,
41+
})?;
42+
Ok(result)
43+
}
44+
}

src/extensions/rust_analyzer.rs

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use crate::{language_client::LanguageClient, types::WorkspaceEditWithCursor};
1+
use crate::types;
2+
use crate::{language_client::LanguageClient, types::WorkspaceEditWithCursor, utils::ToUrl};
23
use anyhow::{anyhow, Result};
34
use jsonrpc_core::Value;
4-
use lsp_types::{Command, Location};
5-
use serde::Deserialize;
5+
use lsp_types::{request::Request, Command, Location, Range, TextDocumentIdentifier};
6+
use serde::{Deserialize, Serialize};
67
use std::path::PathBuf;
78

89
// Runnable wraps the two possible shapes of a runnable action from rust-analyzer. Old-ish versions
@@ -46,6 +47,35 @@ enum GenericRunnableKind {
4647
Cargo,
4748
}
4849

50+
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
51+
pub enum InlayKind {
52+
TypeHint,
53+
ParameterHint,
54+
ChainingHint,
55+
}
56+
57+
#[derive(Debug, Deserialize, Serialize)]
58+
pub struct InlayHint {
59+
pub range: Range,
60+
pub kind: InlayKind,
61+
pub label: String,
62+
}
63+
64+
impl Into<types::InlayHint> for InlayHint {
65+
fn into(self) -> types::InlayHint {
66+
types::InlayHint {
67+
range: self.range,
68+
label: self.label,
69+
}
70+
}
71+
}
72+
73+
#[derive(Serialize, Deserialize)]
74+
#[serde(rename_all = "camelCase")]
75+
pub struct InlayHintsParams {
76+
text_document: TextDocumentIdentifier,
77+
}
78+
4979
pub mod command {
5080
pub const SHOW_REFERENCES: &str = "rust-analyzer.showReferences";
5181
pub const SELECT_APPLY_SOURCE_CHANGE: &str = "rust-analyzer.selectAndApplySourceChange";
@@ -54,7 +84,55 @@ pub mod command {
5484
pub const RUN: &str = "rust-analyzer.run";
5585
}
5686

87+
pub mod request {
88+
pub enum InlayHintsRequest {}
89+
90+
impl lsp_types::request::Request for InlayHintsRequest {
91+
type Params = super::InlayHintsParams;
92+
type Result = Vec<super::InlayHint>;
93+
const METHOD: &'static str = "rust-analyzer/inlayHints";
94+
}
95+
}
96+
97+
const FILETYPE: &'static str = "rust";
98+
pub const SERVER_NAME: &'static str = "rust-analyzer";
99+
57100
impl LanguageClient {
101+
pub fn rust_analyzer_inlay_hints(&self, filename: &str) -> Result<Vec<types::InlayHint>> {
102+
let inlay_hints_enabled = self.get_state(|state| {
103+
state
104+
.initialization_options
105+
.get(SERVER_NAME)
106+
.as_ref()
107+
.map(|opt| {
108+
opt.pointer("/inlayHints/enable")
109+
.unwrap_or(&Value::Bool(false))
110+
== &Value::Bool(true)
111+
})
112+
.unwrap_or_default()
113+
})?;
114+
if !inlay_hints_enabled {
115+
return Ok(vec![]);
116+
}
117+
118+
let result: Vec<InlayHint> = self.get_client(&Some(FILETYPE.into()))?.call(
119+
request::InlayHintsRequest::METHOD,
120+
InlayHintsParams {
121+
text_document: TextDocumentIdentifier {
122+
uri: filename.to_string().to_url()?,
123+
},
124+
},
125+
)?;
126+
127+
// we are only able to display chaining hints at the moment, as we can't place virtual texts in
128+
// between words
129+
Ok(result
130+
.into_iter()
131+
.filter(|h| h.kind == InlayKind::ChainingHint)
132+
.map(InlayHint::into)
133+
.collect())
134+
}
135+
58136
pub fn handle_rust_analyzer_command(&self, cmd: &Command) -> Result<bool> {
59137
match cmd.command.as_str() {
60138
command::SHOW_REFERENCES => {

src/language_server_protocol.rs

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,6 @@ impl LanguageClient {
187187
pub fn apply_workspace_edit(&self, edit: &WorkspaceEdit) -> Result<()> {
188188
let mut filename = self.vim()?.get_filename(&Value::Null)?;
189189
let mut position = self.vim()?.get_position(&Value::Null)?;
190-
191190
if let Some(ref changes) = edit.document_changes {
192191
match changes {
193192
DocumentChanges::Edits(ref changes) => {
@@ -735,13 +734,7 @@ impl LanguageClient {
735734

736735
fn try_handle_command_by_client(&self, cmd: &Command) -> Result<bool> {
737736
let filetype: String = self.vim()?.eval("&filetype")?;
738-
let enabled_extensions = self.get_config(|c| {
739-
c.enable_extensions
740-
.as_ref()
741-
.map(|c| c.get(&filetype).copied().unwrap_or(true))
742-
.unwrap_or(true)
743-
})?;
744-
if !enabled_extensions {
737+
if !self.extensions_enabled(&filetype)? {
745738
return Ok(false);
746739
}
747740

@@ -884,7 +877,7 @@ impl LanguageClient {
884877
/* deprecated in lsp types, but can't initialize without it */
885878
root_path: Some(root.clone()),
886879
root_uri: Some(root.to_url()?),
887-
initialization_options,
880+
initialization_options: initialization_options.clone(),
888881
capabilities: ClientCapabilities {
889882
text_document: Some(TextDocumentClientCapabilities {
890883
color_provider: Some(GenericCapability {
@@ -982,9 +975,21 @@ impl LanguageClient {
982975

983976
let initialize_result = InitializeResult::deserialize(&result)?;
984977
self.update_state(|state| {
978+
let server_name = initialize_result
979+
.server_info
980+
.as_ref()
981+
.map(|info| info.name.clone());
982+
match (server_name, initialization_options) {
983+
(Some(name), Some(options)) => {
984+
state.initialization_options.insert(name, options);
985+
}
986+
_ => {}
987+
}
988+
985989
state
986990
.capabilities
987991
.insert(language_id.clone(), initialize_result);
992+
988993
Ok(())
989994
})?;
990995

@@ -1943,6 +1948,7 @@ impl LanguageClient {
19431948
.notify("s:ExecuteAutocmd", "LanguageClientTextDocumentDidOpenPost")?;
19441949

19451950
self.text_document_code_lens(params)?;
1951+
self.text_document_inlay_hints(&language_id, &filename)?;
19461952

19471953
Ok(())
19481954
}
@@ -1989,7 +1995,7 @@ impl LanguageClient {
19891995
Ok(version)
19901996
})?;
19911997

1992-
self.get_client(&Some(language_id))?.notify(
1998+
self.get_client(&Some(language_id.clone()))?.notify(
19931999
lsp_types::notification::DidChangeTextDocument::METHOD,
19942000
DidChangeTextDocumentParams {
19952001
text_document: VersionedTextDocumentIdentifier {
@@ -2005,6 +2011,7 @@ impl LanguageClient {
20052011
)?;
20062012

20072013
self.text_document_code_lens(params)?;
2014+
self.text_document_inlay_hints(&language_id, &filename)?;
20082015

20092016
Ok(())
20102017
}
@@ -2019,7 +2026,7 @@ impl LanguageClient {
20192026

20202027
let uri = filename.to_url()?;
20212028

2022-
self.get_client(&Some(language_id))?.notify(
2029+
self.get_client(&Some(language_id.clone()))?.notify(
20232030
lsp_types::notification::DidSaveTextDocument::METHOD,
20242031
DidSaveTextDocumentParams {
20252032
text: None,
@@ -2931,15 +2938,25 @@ impl LanguageClient {
29312938

29322939
// code lens
29332940
if UseVirtualText::All == use_virtual_text || UseVirtualText::CodeLens == use_virtual_text {
2934-
virtual_texts.extend(self.virtual_texts_from_code_lenses(filename)?.into_iter());
2941+
virtual_texts.extend(
2942+
self.virtual_texts_from_code_lenses(filename, &viewport)?
2943+
.into_iter(),
2944+
);
2945+
}
2946+
2947+
// inlay hints
2948+
if UseVirtualText::All == use_virtual_text || UseVirtualText::CodeLens == use_virtual_text {
2949+
let additional_virtual_texts =
2950+
self.virtual_texts_from_inlay_hints(filename, &viewport)?;
2951+
virtual_texts.extend(additional_virtual_texts);
29352952
}
29362953

29372954
// diagnostics
29382955
if UseVirtualText::All == use_virtual_text
29392956
|| UseVirtualText::Diagnostics == use_virtual_text
29402957
{
29412958
let vt_diagnostics = self
2942-
.virtual_texts_from_diagnostics(filename, viewport)?
2959+
.virtual_texts_from_diagnostics(filename, &viewport)?
29432960
.into_iter();
29442961
virtual_texts.extend(vt_diagnostics);
29452962
}
@@ -2958,7 +2975,7 @@ impl LanguageClient {
29582975
fn virtual_texts_from_diagnostics(
29592976
&self,
29602977
filename: &str,
2961-
viewport: viewport::Viewport,
2978+
viewport: &viewport::Viewport,
29622979
) -> Result<Vec<VirtualText>> {
29632980
let mut virtual_texts = vec![];
29642981
let diagnostics = self.get_state(|state| state.diagnostics.clone())?;
@@ -2987,11 +3004,52 @@ impl LanguageClient {
29873004
Ok(virtual_texts)
29883005
}
29893006

2990-
fn virtual_texts_from_code_lenses(&self, filename: &str) -> Result<Vec<VirtualText>> {
3007+
fn virtual_texts_from_inlay_hints(
3008+
&self,
3009+
filename: &str,
3010+
viewport: &viewport::Viewport,
3011+
) -> Result<Vec<VirtualText>> {
3012+
let inlay_hints: Vec<InlayHint> = self.get_state(|state| {
3013+
state
3014+
.inlay_hints
3015+
.get(filename)
3016+
.map(|s| {
3017+
s.iter()
3018+
.filter(|hint| viewport.overlaps(hint.range))
3019+
.cloned()
3020+
.collect()
3021+
})
3022+
.unwrap_or_default()
3023+
})?;
3024+
let hl_group = self.get_config(|c| c.code_lens_display.virtual_texthl.clone())?;
3025+
3026+
let virtual_texts = inlay_hints
3027+
.into_iter()
3028+
.map(|hint| VirtualText {
3029+
line: hint.range.end.line,
3030+
text: hint.label,
3031+
hl_group: hl_group.clone(),
3032+
})
3033+
.collect();
3034+
Ok(virtual_texts)
3035+
}
3036+
3037+
fn virtual_texts_from_code_lenses(
3038+
&self,
3039+
filename: &str,
3040+
viewport: &viewport::Viewport,
3041+
) -> Result<Vec<VirtualText>> {
29913042
let mut virtual_texts = vec![];
2992-
let code_lenses =
2993-
self.get_state(|state| state.code_lens.get(filename).cloned().unwrap_or_default())?;
2994-
let code_lens_display = self.get_config(|c| c.code_lens_display.clone())?;
3043+
let code_lenses: Vec<CodeLens> =
3044+
self.get_state(|state| match state.code_lens.get(filename) {
3045+
Some(cls) => cls
3046+
.into_iter()
3047+
.filter(|cl| viewport.overlaps(cl.range))
3048+
.cloned()
3049+
.collect(),
3050+
None => vec![],
3051+
})?;
3052+
let hl_group = self.get_config(|c| c.code_lens_display.virtual_texthl.clone())?;
29953053

29963054
for cl in code_lenses {
29973055
if let Some(command) = cl.command {
@@ -3008,7 +3066,7 @@ impl LanguageClient {
30083066
None => virtual_texts.push(VirtualText {
30093067
line,
30103068
text,
3011-
hl_group: code_lens_display.virtual_texthl.clone(),
3069+
hl_group: hl_group.clone(),
30123070
}),
30133071
}
30143072
}

src/types.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{viewport::Viewport, vim::Highlight};
1010
use anyhow::{anyhow, Result};
1111
use jsonrpc_core::Params;
1212
use log::*;
13+
use lsp_types::Range;
1314
use lsp_types::{
1415
CodeAction, CodeLens, Command, CompletionItem, CompletionTextEdit, Diagnostic,
1516
DiagnosticSeverity, DocumentHighlightKind, FileChangeType, FileEvent, Hover, HoverContents,
@@ -128,6 +129,12 @@ pub enum UseVirtualText {
128129
No,
129130
}
130131

132+
#[derive(Debug, Clone, Deserialize, Serialize)]
133+
pub struct InlayHint {
134+
pub range: Range,
135+
pub label: String,
136+
}
137+
131138
#[derive(Serialize)]
132139
pub struct State {
133140
// Program state.
@@ -156,6 +163,8 @@ pub struct State {
156163
pub diagnostics: HashMap<String, Vec<Diagnostic>>,
157164
// filename => codeLens.
158165
pub code_lens: HashMap<String, Vec<CodeLens>>,
166+
// filename => inlayHint.
167+
pub inlay_hints: HashMap<String, Vec<InlayHint>>,
159168
#[serde(skip_serializing)]
160169
pub line_diagnostics: HashMap<(String, u64), String>,
161170
pub namespace_ids: HashMap<String, i64>,
@@ -175,6 +184,7 @@ pub struct State {
175184
pub stashed_code_action_actions: Vec<CodeAction>,
176185

177186
pub logger: Logger,
187+
pub initialization_options: HashMap<String, Value>,
178188
}
179189

180190
impl State {
@@ -197,6 +207,7 @@ impl State {
197207
semantic_scopes: HashMap::new(),
198208
semantic_scope_to_hl_group_table: HashMap::new(),
199209
semantic_highlights: HashMap::new(),
210+
inlay_hints: HashMap::new(),
200211
code_lens: HashMap::new(),
201212
diagnostics: HashMap::new(),
202213
line_diagnostics: HashMap::new(),
@@ -211,6 +222,7 @@ impl State {
211222
last_cursor_line: 0,
212223
last_line_diagnostic: " ".into(),
213224
stashed_code_action_actions: vec![],
225+
initialization_options: HashMap::new(),
214226
logger,
215227
}
216228
}

0 commit comments

Comments
 (0)