mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 14:21:24 +00:00
[ty] Close signature help after )
(#20017)
This commit is contained in:
parent
c5e05df966
commit
11f521c768
1 changed files with 95 additions and 23 deletions
|
@ -12,6 +12,7 @@ use crate::{Db, find_node::covering_node};
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::File;
|
||||||
use ruff_db::parsed::parsed_module;
|
use ruff_db::parsed::parsed_module;
|
||||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||||
|
use ruff_python_parser::TokenKind;
|
||||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||||
use ty_python_semantic::ResolvedDefinition;
|
use ty_python_semantic::ResolvedDefinition;
|
||||||
use ty_python_semantic::SemanticModel;
|
use ty_python_semantic::SemanticModel;
|
||||||
|
@ -25,7 +26,7 @@ use ty_python_semantic::types::{
|
||||||
// associated with the __new__ or __init__ call.
|
// associated with the __new__ or __init__ call.
|
||||||
|
|
||||||
/// Information about a function parameter
|
/// Information about a function parameter
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct ParameterDetails {
|
pub struct ParameterDetails {
|
||||||
/// The parameter name (e.g., "param1")
|
/// The parameter name (e.g., "param1")
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -37,7 +38,7 @@ pub struct ParameterDetails {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information about a function signature
|
/// Information about a function signature
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct SignatureDetails {
|
pub struct SignatureDetails {
|
||||||
/// Text representation of the full signature (including input parameters and return type).
|
/// Text representation of the full signature (including input parameters and return type).
|
||||||
pub label: String,
|
pub label: String,
|
||||||
|
@ -51,7 +52,7 @@ pub struct SignatureDetails {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signature help information for function calls
|
/// Signature help information for function calls
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct SignatureHelpInfo {
|
pub struct SignatureHelpInfo {
|
||||||
/// Information about each of the signatures for the function call. We
|
/// Information about each of the signatures for the function call. We
|
||||||
/// need to handle multiple because of unions, overloads, and composite
|
/// need to handle multiple because of unions, overloads, and composite
|
||||||
|
@ -104,22 +105,42 @@ fn get_call_expr(
|
||||||
) -> Option<(&ast::ExprCall, usize)> {
|
) -> Option<(&ast::ExprCall, usize)> {
|
||||||
let root_node: AnyNodeRef = parsed.syntax().into();
|
let root_node: AnyNodeRef = parsed.syntax().into();
|
||||||
|
|
||||||
// Create a range from the offset for the covering_node function.
|
// Find the token under the cursor and use its offset to find the node
|
||||||
// Use length 1 if it fits within the root node, otherwise use zero-length range.
|
let token = parsed
|
||||||
let one_char_range = TextRange::at(offset, TextSize::from(1));
|
.tokens()
|
||||||
let range = if root_node.range().contains_range(one_char_range) {
|
.at_offset(offset)
|
||||||
one_char_range
|
.max_by_key(|token| match token.kind() {
|
||||||
} else {
|
TokenKind::Name
|
||||||
TextRange::at(offset, TextSize::from(0))
|
| TokenKind::String
|
||||||
};
|
| TokenKind::Complex
|
||||||
|
| TokenKind::Float
|
||||||
|
| TokenKind::Int => 1,
|
||||||
|
_ => 0,
|
||||||
|
})?;
|
||||||
|
|
||||||
// Find the covering node at the given position that is a function call.
|
// Find the covering node at the given position that is a function call.
|
||||||
let covering_node = covering_node(root_node, range)
|
let call = covering_node(root_node, token.range())
|
||||||
.find_first(|node| matches!(node, AnyNodeRef::ExprCall(_)))
|
.find_first(|node| {
|
||||||
|
if !node.is_expr_call() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the signature help if the cursor is at the closing parenthesis
|
||||||
|
if token.kind() == TokenKind::Rpar && node.end() == token.end() && offset == token.end()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if token.range().is_empty() && node.end() == token.end() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
})
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
// Get the function call expression.
|
// Get the function call expression.
|
||||||
let AnyNodeRef::ExprCall(call_expr) = covering_node.node() else {
|
let AnyNodeRef::ExprCall(call_expr) = call.node() else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -706,6 +727,57 @@ mod tests {
|
||||||
assert_eq!(result.active_signature, Some(0));
|
assert_eq!(result.active_signature, Some(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signature_help_after_closing_paren_at_end_of_file() {
|
||||||
|
let test = cursor_test(
|
||||||
|
r#"
|
||||||
|
def test(a: int) -> int:
|
||||||
|
return 10
|
||||||
|
|
||||||
|
test("test")<CURSOR>"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should not return a signature help
|
||||||
|
assert_eq!(test.signature_help(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signature_help_after_closing_paren_in_expression() {
|
||||||
|
let test = cursor_test(
|
||||||
|
r#"
|
||||||
|
def test(a: int) -> int:
|
||||||
|
return 10
|
||||||
|
|
||||||
|
test("test")<CURSOR> + 10
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should not return a signature help
|
||||||
|
assert_eq!(test.signature_help(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signature_help_after_closing_paren_nested() {
|
||||||
|
let test = cursor_test(
|
||||||
|
r#"
|
||||||
|
def inner(a: int) -> int:
|
||||||
|
return 10
|
||||||
|
|
||||||
|
def outer(a: int) -> None: ...
|
||||||
|
|
||||||
|
outer(inner("test")<CURSOR> + 10)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should return the outer signature help
|
||||||
|
let help = test.signature_help().expect("Should have outer help");
|
||||||
|
|
||||||
|
assert_eq!(help.signatures.len(), 1);
|
||||||
|
|
||||||
|
let signature = &help.signatures[0];
|
||||||
|
assert_eq!(signature.label, "(a: int) -> None");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn signature_help_stub_to_implementation_mapping() {
|
fn signature_help_stub_to_implementation_mapping() {
|
||||||
// Test that when a function is called from a stub file with no docstring,
|
// Test that when a function is called from a stub file with no docstring,
|
||||||
|
@ -714,22 +786,22 @@ mod tests {
|
||||||
.source(
|
.source(
|
||||||
"main.py",
|
"main.py",
|
||||||
r#"
|
r#"
|
||||||
from lib import func
|
from lib import func
|
||||||
result = func(<CURSOR>
|
result = func(<CURSOR>
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.source(
|
.source(
|
||||||
"lib.pyi",
|
"lib.pyi",
|
||||||
r#"
|
r#"
|
||||||
def func() -> str: ...
|
def func() -> str: ...
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.source(
|
.source(
|
||||||
"lib.py",
|
"lib.py",
|
||||||
r#"
|
r#"
|
||||||
def func() -> str:
|
def func() -> str:
|
||||||
"""This function does something."""
|
"""This function does something."""
|
||||||
return ""
|
return ""
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue