Add inlay parameter name hints for function calls

Signed-off-by: imtsuki <me@qjx.app>
This commit is contained in:
imtsuki 2020-01-15 01:02:01 +08:00
parent d8d8c20077
commit c390e92fdd
6 changed files with 205 additions and 13 deletions

View file

@ -34,6 +34,8 @@ pub struct FunctionSignature {
pub generic_parameters: Vec<String>, pub generic_parameters: Vec<String>,
/// Parameters of the function /// Parameters of the function
pub parameters: Vec<String>, pub parameters: Vec<String>,
/// Parameter names of the function
pub parameter_names: Vec<String>,
/// Optional return type /// Optional return type
pub ret_type: Option<String>, pub ret_type: Option<String>,
/// Where predicates /// Where predicates
@ -75,6 +77,7 @@ impl FunctionSignature {
name: node.name().map(|n| n.text().to_string()), name: node.name().map(|n| n.text().to_string()),
ret_type: node.name().map(|n| n.text().to_string()), ret_type: node.name().map(|n| n.text().to_string()),
parameters: params, parameters: params,
parameter_names: vec![],
generic_parameters: generic_parameters(&node), generic_parameters: generic_parameters(&node),
where_predicates: where_predicates(&node), where_predicates: where_predicates(&node),
doc: None, doc: None,
@ -114,6 +117,7 @@ impl FunctionSignature {
name: Some(name), name: Some(name),
ret_type: None, ret_type: None,
parameters: params, parameters: params,
parameter_names: vec![],
generic_parameters: vec![], generic_parameters: vec![],
where_predicates: vec![], where_predicates: vec![],
doc: None, doc: None,
@ -134,6 +138,7 @@ impl FunctionSignature {
name: node.name().map(|n| n.text().to_string()), name: node.name().map(|n| n.text().to_string()),
ret_type: None, ret_type: None,
parameters: params, parameters: params,
parameter_names: vec![],
generic_parameters: vec![], generic_parameters: vec![],
where_predicates: vec![], where_predicates: vec![],
doc: None, doc: None,
@ -157,6 +162,22 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
res res
} }
fn param_name_list(node: &ast::FnDef) -> Vec<String> {
let mut res = vec![];
if let Some(param_list) = node.param_list() {
if let Some(self_param) = param_list.self_param() {
res.push(self_param.syntax().text().to_string())
}
res.extend(
param_list
.params()
.map(|param| param.pat().unwrap().syntax().text().to_string()),
);
}
res
}
FunctionSignature { FunctionSignature {
kind: CallableKind::Function, kind: CallableKind::Function,
visibility: node.visibility().map(|n| n.syntax().text().to_string()), visibility: node.visibility().map(|n| n.syntax().text().to_string()),
@ -166,6 +187,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
.and_then(|r| r.type_ref()) .and_then(|r| r.type_ref())
.map(|n| n.syntax().text().to_string()), .map(|n| n.syntax().text().to_string()),
parameters: param_list(node), parameters: param_list(node),
parameter_names: param_name_list(node),
generic_parameters: generic_parameters(node), generic_parameters: generic_parameters(node),
where_predicates: where_predicates(node), where_predicates: where_predicates(node),
// docs are processed separately // docs are processed separately

View file

@ -4,15 +4,16 @@ use hir::{HirDisplay, SourceAnalyzer};
use once_cell::unsync::Lazy; use once_cell::unsync::Lazy;
use ra_prof::profile; use ra_prof::profile;
use ra_syntax::{ use ra_syntax::{
ast::{self, AstNode, TypeAscriptionOwner}, ast::{self, ArgListOwner, AstNode, TypeAscriptionOwner},
match_ast, SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange, match_ast, SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange,
}; };
use crate::{db::RootDatabase, FileId}; use crate::{db::RootDatabase, FileId, FunctionSignature};
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum InlayKind { pub enum InlayKind {
TypeHint, TypeHint,
ParameterHint,
} }
#[derive(Debug)] #[derive(Debug)]
@ -87,10 +88,79 @@ fn get_inlay_hints(
.collect(), .collect(),
) )
}, },
ast::CallExpr(it) => {
get_param_name_hints(db, &analyzer, ast::Expr::from(it))
},
ast::MethodCallExpr(it) => {
get_param_name_hints(db, &analyzer, ast::Expr::from(it))
},
_ => None, _ => None,
} }
} }
} }
fn get_param_name_hints(
db: &RootDatabase,
analyzer: &SourceAnalyzer,
expr: ast::Expr,
) -> Option<Vec<InlayHint>> {
let args = match &expr {
ast::Expr::CallExpr(expr) => Some(expr.arg_list()?.args()),
ast::Expr::MethodCallExpr(expr) => Some(expr.arg_list()?.args()),
_ => None,
}?;
let mut parameters = get_fn_signature(db, analyzer, &expr)?.parameter_names.into_iter();
if let ast::Expr::MethodCallExpr(_) = &expr {
parameters.next();
};
let hints = parameters
.zip(args)
.filter_map(|(param, arg)| {
if arg.syntax().kind() == SyntaxKind::LITERAL {
Some((arg.syntax().text_range(), param))
} else {
None
}
})
.map(|(range, param_name)| InlayHint {
range,
kind: InlayKind::ParameterHint,
label: param_name.into(),
})
.collect();
Some(hints)
}
fn get_fn_signature(
db: &RootDatabase,
analyzer: &SourceAnalyzer,
expr: &ast::Expr,
) -> Option<FunctionSignature> {
match expr {
ast::Expr::CallExpr(expr) => {
// FIXME: Type::as_callable is broken for closures
let callable_def = analyzer.type_of(db, &expr.expr()?)?.as_callable()?;
match callable_def {
hir::CallableDef::FunctionId(it) => {
let fn_def = it.into();
Some(FunctionSignature::from_hir(db, fn_def))
}
hir::CallableDef::StructId(it) => FunctionSignature::from_struct(db, it.into()),
hir::CallableDef::EnumVariantId(it) => {
FunctionSignature::from_enum_variant(db, it.into())
}
}
}
ast::Expr::MethodCallExpr(expr) => {
let fn_def = analyzer.resolve_method_call(&expr)?;
Some(FunctionSignature::from_hir(db, fn_def))
}
_ => None,
}
}
fn get_pat_type_hints( fn get_pat_type_hints(
db: &RootDatabase, db: &RootDatabase,
@ -605,4 +675,71 @@ fn main() {
"### "###
); );
} }
#[test]
fn function_call_parameter_hint() {
let (analysis, file_id) = single_file(
r#"
struct Test {}
impl Test {
fn method(&self, param: i32) -> i32 {
param * 2
}
}
fn test_func(foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
foo + bar
}
fn main() {
let not_literal = 1;
let _: i32 = test_func(1, 2, "hello", 3, not_literal);
let t: Test = Test {};
t.method(123);
Test::method(&t, 3456);
}"#,
);
assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
[
InlayHint {
range: [207; 218),
kind: TypeHint,
label: "i32",
},
InlayHint {
range: [251; 252),
kind: ParameterHint,
label: "foo",
},
InlayHint {
range: [254; 255),
kind: ParameterHint,
label: "bar",
},
InlayHint {
range: [257; 264),
kind: ParameterHint,
label: "msg",
},
InlayHint {
range: [266; 267),
kind: ParameterHint,
label: "_",
},
InlayHint {
range: [322; 325),
kind: ParameterHint,
label: "param",
},
InlayHint {
range: [349; 353),
kind: ParameterHint,
label: "param",
},
]
"###
);
}
} }

View file

@ -952,6 +952,7 @@ pub fn handle_inlay_hints(
range: api_type.range.conv_with(&line_index), range: api_type.range.conv_with(&line_index),
kind: match api_type.kind { kind: match api_type.kind {
ra_ide::InlayKind::TypeHint => InlayKind::TypeHint, ra_ide::InlayKind::TypeHint => InlayKind::TypeHint,
ra_ide::InlayKind::ParameterHint => InlayKind::ParameterHint,
}, },
}) })
.collect()) .collect())

View file

@ -197,6 +197,7 @@ pub struct InlayHintsParams {
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] #[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum InlayKind { pub enum InlayKind {
TypeHint, TypeHint,
ParameterHint,
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]

View file

@ -228,7 +228,7 @@
"rust-analyzer.displayInlayHints": { "rust-analyzer.displayInlayHints": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
"description": "Display additional type information in the editor" "description": "Display additional type and parameter information in the editor"
}, },
"rust-analyzer.maxInlayHintLength": { "rust-analyzer.maxInlayHintLength": {
"type": "number", "type": "number",

View file

@ -38,6 +38,12 @@ const typeHintDecorationType = vscode.window.createTextEditorDecorationType({
}, },
}); });
const parameterHintDecorationType = vscode.window.createTextEditorDecorationType({
before: {
color: new vscode.ThemeColor('rust_analyzer.inlayHint'),
}
})
class HintsUpdater { class HintsUpdater {
private pending: Map<string, vscode.CancellationTokenSource> = new Map(); private pending: Map<string, vscode.CancellationTokenSource> = new Map();
private ctx: Ctx; private ctx: Ctx;
@ -55,7 +61,10 @@ class HintsUpdater {
if (this.enabled) { if (this.enabled) {
await this.refresh(); await this.refresh();
} else { } else {
this.allEditors.forEach(it => this.setDecorations(it, [])); this.allEditors.forEach(it => {
this.setTypeDecorations(it, []);
this.setParameterDecorations(it, []);
});
} }
} }
@ -68,15 +77,27 @@ class HintsUpdater {
private async refreshEditor(editor: vscode.TextEditor): Promise<void> { private async refreshEditor(editor: vscode.TextEditor): Promise<void> {
const newHints = await this.queryHints(editor.document.uri.toString()); const newHints = await this.queryHints(editor.document.uri.toString());
if (newHints == null) return; if (newHints == null) return;
const newDecorations = newHints.map(hint => ({ const newTypeDecorations = newHints.filter(hint => hint.kind === 'TypeHint')
range: hint.range, .map(hint => ({
renderOptions: { range: hint.range,
after: { renderOptions: {
contentText: `: ${hint.label}`, after: {
contentText: `: ${hint.label}`,
},
}, },
}, }));
})); this.setTypeDecorations(editor, newTypeDecorations);
this.setDecorations(editor, newDecorations);
const newParameterDecorations = newHints.filter(hint => hint.kind === 'ParameterHint')
.map(hint => ({
range: hint.range,
renderOptions: {
before: {
contentText: `${hint.label}: `,
},
},
}));
this.setParameterDecorations(editor, newParameterDecorations);
} }
private get allEditors(): vscode.TextEditor[] { private get allEditors(): vscode.TextEditor[] {
@ -85,7 +106,7 @@ class HintsUpdater {
); );
} }
private setDecorations( private setTypeDecorations(
editor: vscode.TextEditor, editor: vscode.TextEditor,
decorations: vscode.DecorationOptions[], decorations: vscode.DecorationOptions[],
) { ) {
@ -95,6 +116,16 @@ class HintsUpdater {
); );
} }
private setParameterDecorations(
editor: vscode.TextEditor,
decorations: vscode.DecorationOptions[],
) {
editor.setDecorations(
parameterHintDecorationType,
this.enabled ? decorations : [],
);
}
private async queryHints(documentUri: string): Promise<InlayHint[] | null> { private async queryHints(documentUri: string): Promise<InlayHint[] | null> {
let client = this.ctx.client; let client = this.ctx.client;
if (!client) return null; if (!client) return null;