Skip to content

Commit 8138b1d

Browse files
bors[bot]flodiebold
andcommitted
Merge #1215
1215: Fix hover on the beginning of a nested expression r=matklad a=flodiebold E.g. in ``` let foo = 1u32; if true { <|>foo; } ``` the hover shows `()`, the type of the whole if expression, instead of the more sensible `u32`. The reason for this was that the search for an expression was slightly left-biased: When on the edge between two tokens, it first looked at all ancestors of the left token and then of the right token. Instead merge the ancestors in ascending order, so that we get the smaller of the two possible expressions. This might seem rather inconsequential, but emacs-lsp's sideline requests hover for the beginning of each word in the current line, so it tends to hit this a lot 😄 Actually, as I think about this now, another solution might be just making hover right-biased, since if I'm hovering a certain character I really mean that character and not the one to its left... Co-authored-by: Florian Diebold <[email protected]>
2 parents 1dd3d9b + 8563365 commit 8138b1d

File tree

3 files changed

+33
-10
lines changed

3 files changed

+33
-10
lines changed

crates/ra_ide_api/src/goto_definition.rs

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub(crate) fn goto_definition(
2525
None
2626
}
2727

28+
#[derive(Debug)]
2829
pub(crate) enum ReferenceResult {
2930
Exact(NavigationTarget),
3031
Approximate(Vec<NavigationTarget>),

crates/ra_ide_api/src/hover.rs

+14-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use ra_db::SourceDatabase;
22
use ra_syntax::{
33
AstNode, ast,
4-
algo::{find_covering_element, find_node_at_offset, find_token_at_offset},
4+
algo::{find_covering_element, find_node_at_offset, ancestors_at_offset},
55
};
66
use hir::HirDisplay;
77

@@ -104,12 +104,8 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
104104
}
105105

106106
if range.is_none() {
107-
let node = find_token_at_offset(file.syntax(), position.offset).find_map(|token| {
108-
token
109-
.parent()
110-
.ancestors()
111-
.find(|n| ast::Expr::cast(*n).is_some() || ast::Pat::cast(*n).is_some())
112-
})?;
107+
let node = ancestors_at_offset(file.syntax(), position.offset)
108+
.find(|n| ast::Expr::cast(*n).is_some() || ast::Pat::cast(*n).is_some())?;
113109
let frange = FileRange { file_id: position.file_id, range: node.range() };
114110
res.extend(type_of(db, frange).map(rust_code_markup));
115111
range = Some(node.range());
@@ -397,6 +393,17 @@ The Some variant
397393
assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
398394
}
399395

396+
#[test]
397+
fn hover_local_var_edge() {
398+
let (analysis, position) = single_file_with_position(
399+
"
400+
fn func(foo: i32) { if true { <|>foo; }; }
401+
",
402+
);
403+
let hover = analysis.hover(position).unwrap().unwrap();
404+
assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
405+
}
406+
400407
#[test]
401408
fn test_type_of_for_function() {
402409
let (analysis, range) = single_file_with_range(

crates/ra_syntax/src/algo.rs

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
pub mod visit;
22

3+
use itertools::Itertools;
4+
35
use crate::{SyntaxNode, TextRange, TextUnit, AstNode, Direction, SyntaxToken, SyntaxElement};
46

57
pub use rowan::TokenAtOffset;
@@ -12,6 +14,20 @@ pub fn find_token_at_offset(node: &SyntaxNode, offset: TextUnit) -> TokenAtOffse
1214
}
1315
}
1416

17+
/// Returns ancestors of the node at the offset, sorted by length. This should
18+
/// do the right thing at an edge, e.g. when searching for expressions at `{
19+
/// <|>foo }` we will get the name reference instead of the whole block, which
20+
/// we would get if we just did `find_token_at_offset(...).flat_map(|t|
21+
/// t.parent().ancestors())`.
22+
pub fn ancestors_at_offset(
23+
node: &SyntaxNode,
24+
offset: TextUnit,
25+
) -> impl Iterator<Item = &SyntaxNode> {
26+
find_token_at_offset(node, offset)
27+
.map(|token| token.parent().ancestors())
28+
.kmerge_by(|node1, node2| node1.range().len() < node2.range().len())
29+
}
30+
1531
/// Finds a node of specific Ast type at offset. Note that this is slightly
1632
/// imprecise: if the cursor is strictly between two nodes of the desired type,
1733
/// as in
@@ -20,10 +36,9 @@ pub fn find_token_at_offset(node: &SyntaxNode, offset: TextUnit) -> TokenAtOffse
2036
/// struct Foo {}|struct Bar;
2137
/// ```
2238
///
23-
/// then the left node will be silently preferred.
39+
/// then the shorter node will be silently preferred.
2440
pub fn find_node_at_offset<N: AstNode>(syntax: &SyntaxNode, offset: TextUnit) -> Option<&N> {
25-
find_token_at_offset(syntax, offset)
26-
.find_map(|leaf| leaf.parent().ancestors().find_map(N::cast))
41+
ancestors_at_offset(syntax, offset).find_map(N::cast)
2742
}
2843

2944
/// Finds the first sibling in the given direction which is not `trivia`

0 commit comments

Comments
 (0)