mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-28 12:54:58 +00:00
Add inlay parameter name hints for function calls
Signed-off-by: imtsuki <me@qjx.app>
This commit is contained in:
parent
d8d8c20077
commit
c390e92fdd
6 changed files with 205 additions and 13 deletions
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue