mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-11-03 05:13:00 +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;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue