aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide_api/src/inlay_hints.rs180
-rw-r--r--crates/ra_ide_api/src/lib.rs7
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs1
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs23
-rw-r--r--crates/ra_lsp_server/src/req.rs27
5 files changed, 237 insertions, 1 deletions
diff --git a/crates/ra_ide_api/src/inlay_hints.rs b/crates/ra_ide_api/src/inlay_hints.rs
new file mode 100644
index 000000000..174662beb
--- /dev/null
+++ b/crates/ra_ide_api/src/inlay_hints.rs
@@ -0,0 +1,180 @@
1use crate::{db::RootDatabase, FileId};
2use hir::{HirDisplay, Ty};
3use ra_syntax::ast::Pat;
4use ra_syntax::{
5 algo::visit::{visitor, Visitor},
6 ast::{self, PatKind, TypeAscriptionOwner},
7 AstNode, SmolStr, SourceFile, SyntaxNode, TextRange,
8};
9
10#[derive(Debug, PartialEq, Eq)]
11pub enum InlayKind {
12 LetBindingType,
13 ClosureParameterType,
14}
15
16#[derive(Debug)]
17pub struct InlayHint {
18 pub range: TextRange,
19 pub kind: InlayKind,
20 pub label: SmolStr,
21}
22
23pub(crate) fn inlay_hints(db: &RootDatabase, file_id: FileId, file: &SourceFile) -> Vec<InlayHint> {
24 file.syntax()
25 .descendants()
26 .map(|node| get_inlay_hints(db, file_id, &node).unwrap_or_default())
27 .flatten()
28 .collect()
29}
30
31fn get_inlay_hints(
32 db: &RootDatabase,
33 file_id: FileId,
34 node: &SyntaxNode,
35) -> Option<Vec<InlayHint>> {
36 visitor()
37 .visit(|let_statement: ast::LetStmt| {
38 let let_syntax = let_statement.syntax();
39
40 if let_statement.ascribed_type().is_some() {
41 return None;
42 }
43
44 let let_pat = let_statement.pat()?;
45 let inlay_type_string = get_node_displayable_type(db, file_id, let_syntax, &let_pat)?
46 .display(db)
47 .to_string()
48 .into();
49
50 let pat_range = match let_pat.kind() {
51 PatKind::BindPat(bind_pat) => bind_pat.syntax().text_range(),
52 PatKind::TuplePat(tuple_pat) => tuple_pat.syntax().text_range(),
53 _ => return None,
54 };
55
56 Some(vec![InlayHint {
57 range: pat_range,
58 kind: InlayKind::LetBindingType,
59 label: inlay_type_string,
60 }])
61 })
62 .visit(|closure_parameter: ast::LambdaExpr| match closure_parameter.param_list() {
63 Some(param_list) => Some(
64 param_list
65 .params()
66 .filter(|closure_param| closure_param.ascribed_type().is_none())
67 .filter_map(|closure_param| {
68 let closure_param_syntax = closure_param.syntax();
69 let inlay_type_string = get_node_displayable_type(
70 db,
71 file_id,
72 closure_param_syntax,
73 &closure_param.pat()?,
74 )?
75 .display(db)
76 .to_string()
77 .into();
78
79 Some(InlayHint {
80 range: closure_param_syntax.text_range(),
81 kind: InlayKind::ClosureParameterType,
82 label: inlay_type_string,
83 })
84 })
85 .collect(),
86 ),
87 None => None,
88 })
89 .accept(&node)?
90}
91
92fn get_node_displayable_type(
93 db: &RootDatabase,
94 file_id: FileId,
95 node_syntax: &SyntaxNode,
96 node_pat: &Pat,
97) -> Option<Ty> {
98 let analyzer = hir::SourceAnalyzer::new(db, file_id, node_syntax, None);
99 analyzer.type_of_pat(db, node_pat).and_then(|resolved_type| {
100 if let Ty::Apply(_) = resolved_type {
101 Some(resolved_type)
102 } else {
103 None
104 }
105 })
106}
107
108#[cfg(test)]
109mod tests {
110 use crate::mock_analysis::single_file;
111 use insta::assert_debug_snapshot_matches;
112
113 #[test]
114 fn test_inlay_hints() {
115 let (analysis, file_id) = single_file(
116 r#"
117struct OuterStruct {}
118
119fn main() {
120 struct InnerStruct {}
121
122 let test = 54;
123 let test = InnerStruct {};
124 let test = OuterStruct {};
125 let test = vec![222];
126 let mut test = Vec::new();
127 test.push(333);
128 let test = test.into_iter().map(|i| i * i).collect::<Vec<_>>();
129 let mut test = 33;
130 let _ = 22;
131 let test: Vec<_> = (0..3).collect();
132
133 let _ = (0..23).map(|i: u32| {
134 let i_squared = i * i;
135 i_squared
136 });
137
138 let test: i32 = 33;
139
140 let (x, c) = (42, 'a');
141 let test = (42, 'a');
142}
143"#,
144 );
145
146 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
147 InlayHint {
148 range: [71; 75),
149 kind: LetBindingType,
150 label: "i32",
151 },
152 InlayHint {
153 range: [121; 125),
154 kind: LetBindingType,
155 label: "OuterStruct",
156 },
157 InlayHint {
158 range: [297; 305),
159 kind: LetBindingType,
160 label: "i32",
161 },
162 InlayHint {
163 range: [417; 426),
164 kind: LetBindingType,
165 label: "u32",
166 },
167 InlayHint {
168 range: [496; 502),
169 kind: LetBindingType,
170 label: "(i32, char)",
171 },
172 InlayHint {
173 range: [524; 528),
174 kind: LetBindingType,
175 label: "(i32, char)",
176 },
177]"#
178 );
179 }
180}
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs
index c54d574bc..16ffb03ce 100644
--- a/crates/ra_ide_api/src/lib.rs
+++ b/crates/ra_ide_api/src/lib.rs
@@ -38,6 +38,7 @@ mod join_lines;
38mod typing; 38mod typing;
39mod matching_brace; 39mod matching_brace;
40mod display; 40mod display;
41mod inlay_hints;
41 42
42#[cfg(test)] 43#[cfg(test)]
43mod marks; 44mod marks;
@@ -64,6 +65,7 @@ pub use crate::{
64 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, 65 display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
65 folding_ranges::{Fold, FoldKind}, 66 folding_ranges::{Fold, FoldKind},
66 hover::HoverResult, 67 hover::HoverResult,
68 inlay_hints::{InlayHint, InlayKind},
67 line_index::{LineCol, LineIndex}, 69 line_index::{LineCol, LineIndex},
68 line_index_utils::translate_offset_with_edit, 70 line_index_utils::translate_offset_with_edit,
69 references::ReferenceSearchResult, 71 references::ReferenceSearchResult,
@@ -396,6 +398,11 @@ impl Analysis {
396 file_structure(&parse.tree()) 398 file_structure(&parse.tree())
397 } 399 }
398 400
401 /// Returns a list of the places in the file where type hints can be displayed.
402 pub fn inlay_hints(&self, file_id: FileId) -> Cancelable<Vec<InlayHint>> {
403 self.with_db(|db| inlay_hints::inlay_hints(db, file_id, &db.parse(file_id).tree()))
404 }
405
399 /// Returns the set of folding ranges. 406 /// Returns the set of folding ranges.
400 pub fn folding_ranges(&self, file_id: FileId) -> Vec<Fold> { 407 pub fn folding_ranges(&self, file_id: FileId) -> Vec<Fold> {
401 let parse = self.db.parse(file_id); 408 let parse = self.db.parse(file_id);
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index 668d2fd72..8e830c8b8 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -362,6 +362,7 @@ fn on_request(
362 .on::<req::References>(handlers::handle_references)? 362 .on::<req::References>(handlers::handle_references)?
363 .on::<req::Formatting>(handlers::handle_formatting)? 363 .on::<req::Formatting>(handlers::handle_formatting)?
364 .on::<req::DocumentHighlightRequest>(handlers::handle_document_highlight)? 364 .on::<req::DocumentHighlightRequest>(handlers::handle_document_highlight)?
365 .on::<req::InlayHints>(handlers::handle_inlay_hints)?
365 .finish(); 366 .finish();
366 Ok(()) 367 Ok(())
367} 368}
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 68865b755..5bf950a53 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -21,7 +21,7 @@ use url_serde::Ser;
21use crate::{ 21use crate::{
22 cargo_target_spec::{runnable_args, CargoTargetSpec}, 22 cargo_target_spec::{runnable_args, CargoTargetSpec},
23 conv::{to_location, Conv, ConvWith, MapConvWith, TryConvWith, TryConvWithToVec}, 23 conv::{to_location, Conv, ConvWith, MapConvWith, TryConvWith, TryConvWithToVec},
24 req::{self, Decoration}, 24 req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind},
25 world::WorldSnapshot, 25 world::WorldSnapshot,
26 LspError, Result, 26 LspError, Result,
27}; 27};
@@ -874,3 +874,24 @@ fn to_diagnostic_severity(severity: Severity) -> DiagnosticSeverity {
874 WeakWarning => DiagnosticSeverity::Hint, 874 WeakWarning => DiagnosticSeverity::Hint,
875 } 875 }
876} 876}
877
878pub fn handle_inlay_hints(
879 world: WorldSnapshot,
880 params: InlayHintsParams,
881) -> Result<Vec<InlayHint>> {
882 let file_id = params.text_document.try_conv_with(&world)?;
883 let analysis = world.analysis();
884 let line_index = analysis.file_line_index(file_id);
885 Ok(analysis
886 .inlay_hints(file_id)?
887 .into_iter()
888 .map(|api_type| InlayHint {
889 label: api_type.label.to_string(),
890 range: api_type.range.conv_with(&line_index),
891 kind: match api_type.kind {
892 ra_ide_api::InlayKind::LetBindingType => InlayKind::LetBindingType,
893 ra_ide_api::InlayKind::ClosureParameterType => InlayKind::ClosureParameterType,
894 },
895 })
896 .collect())
897}
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs
index 8d39b04a7..916185f99 100644
--- a/crates/ra_lsp_server/src/req.rs
+++ b/crates/ra_lsp_server/src/req.rs
@@ -196,3 +196,30 @@ pub struct SourceChange {
196 pub workspace_edit: WorkspaceEdit, 196 pub workspace_edit: WorkspaceEdit,
197 pub cursor_position: Option<TextDocumentPositionParams>, 197 pub cursor_position: Option<TextDocumentPositionParams>,
198} 198}
199
200pub enum InlayHints {}
201
202impl Request for InlayHints {
203 type Params = InlayHintsParams;
204 type Result = Vec<InlayHint>;
205 const METHOD: &'static str = "rust-analyzer/inlayHints";
206}
207
208#[derive(Serialize, Deserialize, Debug)]
209#[serde(rename_all = "camelCase")]
210pub struct InlayHintsParams {
211 pub text_document: TextDocumentIdentifier,
212}
213
214#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
215pub enum InlayKind {
216 LetBindingType,
217 ClosureParameterType,
218}
219
220#[derive(Debug, Deserialize, Serialize)]
221pub struct InlayHint {
222 pub range: Range,
223 pub kind: InlayKind,
224 pub label: String,
225}