Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display signature when hovering over function definitions or module constants #2302

Merged
merged 11 commits into from Aug 30, 2023

Conversation

nino
Copy link
Contributor

@nino nino commented Aug 19, 2023

Hi!

I had a go at #2241.

Hovering over constants now displays their type:
CleanShot 2023-08-19 at 19 28 41@2x

And hovering over function definitions displays their signature:
CleanShot 2023-08-19 at 19 31 10@2x

Happy to make any changes you need to this PR :)

Thanks!

@nino
Copy link
Contributor Author

nino commented Aug 19, 2023

On closer inspection I noticed hovering over function arguments still just shows the whole function signature. (See picture.)

CleanShot 2023-08-19 at 21 32 20@2x

I'll try to handle hovering on function arguments too.

EDIT: I made things maybe a bit uglier by passing byte_index to hover_for_module_statement, but now it should work correctly when hovering over a function argument (showing its type) and hovering over the function body (showing nothing).

Now hovering over the function body is ignored, and hovering over an
argument will display just the type of that argument. Hovering anywhere
else in the function head will display the signature.
Copy link
Member

@lpil lpil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fab! Thank you. I've left a note inline, and we'll need tests for the new functionality.

range: Some(src_span_to_lsp_range(arg.location, &line_numbers)),
});
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This finding logic should live with the other finding logic in node_at_position. This function is only about showing the information rather than hunting through the AST.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would involve extending the Located enum, right? I can have a go at that in the next few days (and also try to write some tests 🤞)

Now the hover_for_... functions don't do a bunch of unpacking of the AST
nodes anymore, but it's still happening in the `hover` function. To move
the functionality into `node_at_position`, I think we'd have to change
the `Located` enum.
} else {
Some(hover_for_function_head(fun, lines))
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This finding logic should go in the node_at_position functions with the other finding logic that deals with byte indexes and AST. This function here doesn't perform any finding logic, it just maps from a found node to a hover response function.

Sorry, I was unclear previously!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds like the best way to do this, but I wanted to check if my understanding/approach sounds reasonable before I try to do stuff (also this is my first open-source contribution of non-trivial size, so this is a bit scary).

Does this sound right:

node_at_position should be capable of finding the function head and each function arg. The main type node_at_position returns is Located, and I don't think there's a way to represent a function argument in a Located. So I think I'd add an entry to Located called Arg(&'a TypedArg). Then in TypedDefinition::find_node, I'd try to return a Located::Arg if possible, and a Located::ModuleStatement if not?

(And also I'll look at how to write tests for the new behavior.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup! That's perfect

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there still some location logic in this function? What is the case in which it is needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The remaining logic is to prevent the function signature coming up when you hover leading/trailing whitespace or comments in the function body. It's essentially undoing the .full_location() logic in ast.rs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lpil I added another commit removing all the finding logic from the hover function by adding a separate FunctionBody entry to Located. So now looking at a function head will return Located::ModuleStatement(Definition::Function(fun)), and looking at the function body will return Located::FunctionBody(fun).

I wonder if maybe there should be separate AST nodes for function body
and function head.
@nino nino marked this pull request as draft August 23, 2023 17:18
@nino nino marked this pull request as ready for review August 26, 2023 09:57
@nino
Copy link
Contributor Author

nino commented Aug 26, 2023

I think this might be ready for another look. I've tidied things up a bit and added some tests.

The main issues now are:

  1. The hover function still does a bit of checking whether we're inside the function head or body:
Located::ModuleStatement(Definition::Function(fun)) => {
    if fun.location.contains(byte_index) {
        Some(hover_for_function_head(fun, lines))
    } else {
        // Don't show hover info for function body
        None
    }
}

Which could be fixed, but I'm not sure if that would break any assumptions we're making about find_node finding a Function when looking at a position inside the function body.

  1. There are two places where I'm defining a temporary variable let empty_smolstr = SmolStr::from("");, because I couldn't figure out how to do unwrap_or_default with SmolStrs. Maybe there's a better solution for that.

Copy link
Member

@lpil lpil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking really nice! I've left a couple tiny questions inline.

Would you mind also updating the CHANGELOG file? Thank you

Comment on lines +497 to +498
let empty_smolstr = SmolStr::from("");
let documentation = fun.documentation.as_ref().unwrap_or(&empty_smolstr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does .unwrap_or_default() not work here? Or .unwrap_or("".into())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.unwrap_or_default() was the first thing I'd tried since you were using it for all the results of .get_documentation(). That didn't work because .documentation itself is a SmolStr, and &SmolStr doesn't have Default implemented.

Trying some more stuff, the only other way I can see is

    let documentation = fun
        .documentation
        .as_ref()
        .map(|s| s.as_str())
        .unwrap_or_default();

which feels "cleaner" in some way, but also less readable?

Copy link
Member

@lpil lpil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic!! Thank you so much

@lpil lpil enabled auto-merge (rebase) August 30, 2023 11:25
auto-merge was automatically disabled August 30, 2023 11:35

Rebase failed

@lpil
Copy link
Member

lpil commented Aug 30, 2023

Would you like to rebase the branch to remove the merge commits and conflicts, or should I squash merge?

@nino
Copy link
Contributor Author

nino commented Aug 30, 2023

Probably easiest if you just squash merge? But I can also do a rebase if that's the preferred workflow

@lpil
Copy link
Member

lpil commented Aug 30, 2023

Works for me! Thanks

@lpil lpil merged commit 7865442 into gleam-lang:main Aug 30, 2023
10 checks passed
@nino nino deleted the 2241-hovering-over-function-definition branch August 30, 2023 11:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants