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::parsed::parsed_module;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_python_parser::TokenKind;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::ResolvedDefinition;
|
||||
use ty_python_semantic::SemanticModel;
|
||||
|
@ -25,7 +26,7 @@ use ty_python_semantic::types::{
|
|||
// associated with the __new__ or __init__ call.
|
||||
|
||||
/// Information about a function parameter
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ParameterDetails {
|
||||
/// The parameter name (e.g., "param1")
|
||||
pub name: String,
|
||||
|
@ -37,7 +38,7 @@ pub struct ParameterDetails {
|
|||
}
|
||||
|
||||
/// Information about a function signature
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SignatureDetails {
|
||||
/// Text representation of the full signature (including input parameters and return type).
|
||||
pub label: String,
|
||||
|
@ -51,7 +52,7 @@ pub struct SignatureDetails {
|
|||
}
|
||||
|
||||
/// Signature help information for function calls
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SignatureHelpInfo {
|
||||
/// Information about each of the signatures for the function call. We
|
||||
/// need to handle multiple because of unions, overloads, and composite
|
||||
|
@ -104,22 +105,42 @@ fn get_call_expr(
|
|||
) -> Option<(&ast::ExprCall, usize)> {
|
||||
let root_node: AnyNodeRef = parsed.syntax().into();
|
||||
|
||||
// Create a range from the offset for the covering_node function.
|
||||
// Use length 1 if it fits within the root node, otherwise use zero-length range.
|
||||
let one_char_range = TextRange::at(offset, TextSize::from(1));
|
||||
let range = if root_node.range().contains_range(one_char_range) {
|
||||
one_char_range
|
||||
} else {
|
||||
TextRange::at(offset, TextSize::from(0))
|
||||
};
|
||||
// Find the token under the cursor and use its offset to find the node
|
||||
let token = parsed
|
||||
.tokens()
|
||||
.at_offset(offset)
|
||||
.max_by_key(|token| match token.kind() {
|
||||
TokenKind::Name
|
||||
| TokenKind::String
|
||||
| TokenKind::Complex
|
||||
| TokenKind::Float
|
||||
| TokenKind::Int => 1,
|
||||
_ => 0,
|
||||
})?;
|
||||
|
||||
// Find the covering node at the given position that is a function call.
|
||||
let covering_node = covering_node(root_node, range)
|
||||
.find_first(|node| matches!(node, AnyNodeRef::ExprCall(_)))
|
||||
let call = covering_node(root_node, token.range())
|
||||
.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()?;
|
||||
|
||||
// Get the function call expression.
|
||||
let AnyNodeRef::ExprCall(call_expr) = covering_node.node() else {
|
||||
let AnyNodeRef::ExprCall(call_expr) = call.node() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
|
@ -247,11 +268,11 @@ mod tests {
|
|||
r#"
|
||||
def example_function(param1: str, param2: int) -> str:
|
||||
"""This is a docstring for the example function.
|
||||
|
||||
|
||||
Args:
|
||||
param1: The first parameter as a string
|
||||
param2: The second parameter as an integer
|
||||
|
||||
|
||||
Returns:
|
||||
A formatted string combining both parameters
|
||||
"""
|
||||
|
@ -627,7 +648,7 @@ mod tests {
|
|||
r#"
|
||||
def documented_function(param1: str, param2: int) -> str:
|
||||
"""This is a function with parameter documentation.
|
||||
|
||||
|
||||
Args:
|
||||
param1: The first parameter description
|
||||
param2: The second parameter description
|
||||
|
@ -706,6 +727,57 @@ mod tests {
|
|||
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]
|
||||
fn signature_help_stub_to_implementation_mapping() {
|
||||
// Test that when a function is called from a stub file with no docstring,
|
||||
|
@ -714,22 +786,22 @@ mod tests {
|
|||
.source(
|
||||
"main.py",
|
||||
r#"
|
||||
from lib import func
|
||||
result = func(<CURSOR>
|
||||
from lib import func
|
||||
result = func(<CURSOR>
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"lib.pyi",
|
||||
r#"
|
||||
def func() -> str: ...
|
||||
def func() -> str: ...
|
||||
"#,
|
||||
)
|
||||
.source(
|
||||
"lib.py",
|
||||
r#"
|
||||
def func() -> str:
|
||||
"""This function does something."""
|
||||
return ""
|
||||
def func() -> str:
|
||||
"""This function does something."""
|
||||
return ""
|
||||
"#,
|
||||
)
|
||||
.build();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue