mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-03 07:04:49 +00:00
Merge #9693
9693: feat: Add the Hover Range capability which enables showing the type of an expression r=matklad a=alexfertel Closes https://github.com/rust-analyzer/rust-analyzer/issues/389 This PR extends the `textDocument/hover` method to allow getting the type of an expression. It looks like this:  Edit: One thing I noticed is that when hovering a selection that includes a macro it doesn't work, so maybe this would need a follow-up issue discussing what problem that may have. (PS: What a great project! I am learning a lot! 🚀) Co-authored-by: Alexander Gonzalez <alexfertel97@gmail.com> Co-authored-by: Alexander González <alexfertel97@gmail.com>
This commit is contained in:
commit
068ede0991
11 changed files with 302 additions and 34 deletions
|
@ -233,7 +233,7 @@ impl TestDB {
|
||||||
events
|
events
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|e| match e.kind {
|
.filter_map(|e| match e.kind {
|
||||||
// This pretty horrible, but `Debug` is the only way to inspect
|
// This is pretty horrible, but `Debug` is the only way to inspect
|
||||||
// QueryDescriptor at the moment.
|
// QueryDescriptor at the moment.
|
||||||
salsa::EventKind::WillExecute { database_key } => {
|
salsa::EventKind::WillExecute { database_key } => {
|
||||||
Some(format!("{:?}", database_key.debug(self)))
|
Some(format!("{:?}", database_key.debug(self)))
|
||||||
|
|
|
@ -138,7 +138,7 @@ impl TestDB {
|
||||||
events
|
events
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|e| match e.kind {
|
.filter_map(|e| match e.kind {
|
||||||
// This pretty horrible, but `Debug` is the only way to inspect
|
// This is pretty horrible, but `Debug` is the only way to inspect
|
||||||
// QueryDescriptor at the moment.
|
// QueryDescriptor at the moment.
|
||||||
salsa::EventKind::WillExecute { database_key } => {
|
salsa::EventKind::WillExecute { database_key } => {
|
||||||
Some(format!("{:?}", database_key.debug(self)))
|
Some(format!("{:?}", database_key.debug(self)))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics};
|
use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics};
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
base_db::SourceDatabase,
|
base_db::{FileRange, SourceDatabase},
|
||||||
defs::{Definition, NameClass, NameRefClass},
|
defs::{Definition, NameClass, NameRefClass},
|
||||||
helpers::{
|
helpers::{
|
||||||
generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
|
generated_lints::{CLIPPY_LINTS, DEFAULT_LINTS, FEATURES},
|
||||||
|
@ -12,8 +12,12 @@ use ide_db::{
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use stdx::format_to;
|
use stdx::format_to;
|
||||||
use syntax::{
|
use syntax::{
|
||||||
algo, ast, display::fn_as_proc_macro_label, match_ast, AstNode, AstToken, Direction,
|
algo::{self, find_node_at_range},
|
||||||
SyntaxKind::*, SyntaxToken, T,
|
ast,
|
||||||
|
display::fn_as_proc_macro_label,
|
||||||
|
match_ast, AstNode, AstToken, Direction,
|
||||||
|
SyntaxKind::*,
|
||||||
|
SyntaxToken, T,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -69,17 +73,39 @@ pub struct HoverResult {
|
||||||
|
|
||||||
// Feature: Hover
|
// Feature: Hover
|
||||||
//
|
//
|
||||||
// Shows additional information, like type of an expression or documentation for definition when "focusing" code.
|
// Shows additional information, like the type of an expression or the documentation for a definition when "focusing" code.
|
||||||
// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
|
// Focusing is usually hovering with a mouse, but can also be triggered with a shortcut.
|
||||||
//
|
//
|
||||||
// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
|
// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[]
|
||||||
pub(crate) fn hover(
|
pub(crate) fn hover(
|
||||||
db: &RootDatabase,
|
db: &RootDatabase,
|
||||||
position: FilePosition,
|
range: FileRange,
|
||||||
config: &HoverConfig,
|
config: &HoverConfig,
|
||||||
) -> Option<RangeInfo<HoverResult>> {
|
) -> Option<RangeInfo<HoverResult>> {
|
||||||
let sema = hir::Semantics::new(db);
|
let sema = hir::Semantics::new(db);
|
||||||
let file = sema.parse(position.file_id).syntax().clone();
|
let file = sema.parse(range.file_id).syntax().clone();
|
||||||
|
|
||||||
|
// This means we're hovering over a range.
|
||||||
|
if !range.range.is_empty() {
|
||||||
|
let expr = find_node_at_range::<ast::Expr>(&file, range.range)?;
|
||||||
|
let ty = sema.type_of_expr(&expr)?;
|
||||||
|
|
||||||
|
if ty.is_unknown() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut res = HoverResult::default();
|
||||||
|
|
||||||
|
res.markup = if config.markdown() {
|
||||||
|
Markup::fenced_block(&ty.display(db))
|
||||||
|
} else {
|
||||||
|
ty.display(db).to_string().into()
|
||||||
|
};
|
||||||
|
|
||||||
|
return Some(RangeInfo::new(range.range, res));
|
||||||
|
}
|
||||||
|
|
||||||
|
let position = FilePosition { file_id: range.file_id, offset: range.range.start() };
|
||||||
let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
|
let token = pick_best_token(file.token_at_offset(position.offset), |kind| match kind {
|
||||||
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
|
IDENT | INT_NUMBER | LIFETIME_IDENT | T![self] | T![super] | T![crate] => 3,
|
||||||
T!['('] | T![')'] => 2,
|
T!['('] | T![')'] => 2,
|
||||||
|
@ -94,8 +120,8 @@ pub(crate) fn hover(
|
||||||
let mut range = None;
|
let mut range = None;
|
||||||
let definition = match_ast! {
|
let definition = match_ast! {
|
||||||
match node {
|
match node {
|
||||||
// we don't use NameClass::referenced_or_defined here as we do not want to resolve
|
// We don't use NameClass::referenced_or_defined here as we do not want to resolve
|
||||||
// field pattern shorthands to their definition
|
// field pattern shorthands to their definition.
|
||||||
ast::Name(name) => NameClass::classify(&sema, &name).map(|class| match class {
|
ast::Name(name) => NameClass::classify(&sema, &name).map(|class| match class {
|
||||||
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
|
NameClass::Definition(it) | NameClass::ConstReference(it) => it,
|
||||||
NameClass::PatFieldShorthand { local_def, field_ref: _ } => Definition::Local(local_def),
|
NameClass::PatFieldShorthand { local_def, field_ref: _ } => Definition::Local(local_def),
|
||||||
|
@ -193,6 +219,7 @@ pub(crate) fn hover(
|
||||||
} else {
|
} else {
|
||||||
ty.display(db).to_string().into()
|
ty.display(db).to_string().into()
|
||||||
};
|
};
|
||||||
|
|
||||||
let range = sema.original_range(&node).range;
|
let range = sema.original_range(&node).range;
|
||||||
Some(RangeInfo::new(range, res))
|
Some(RangeInfo::new(range, res))
|
||||||
}
|
}
|
||||||
|
@ -530,7 +557,8 @@ fn find_std_module(famous_defs: &FamousDefs, name: &str) -> Option<hir::Module>
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use expect_test::{expect, Expect};
|
use expect_test::{expect, Expect};
|
||||||
use ide_db::base_db::FileLoader;
|
use ide_db::base_db::{FileLoader, FileRange};
|
||||||
|
use syntax::TextRange;
|
||||||
|
|
||||||
use crate::{fixture, hover::HoverDocFormat, HoverConfig};
|
use crate::{fixture, hover::HoverDocFormat, HoverConfig};
|
||||||
|
|
||||||
|
@ -542,7 +570,7 @@ mod tests {
|
||||||
links_in_hover: true,
|
links_in_hover: true,
|
||||||
documentation: Some(HoverDocFormat::Markdown),
|
documentation: Some(HoverDocFormat::Markdown),
|
||||||
},
|
},
|
||||||
position,
|
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(hover.is_none());
|
assert!(hover.is_none());
|
||||||
|
@ -556,7 +584,7 @@ mod tests {
|
||||||
links_in_hover: true,
|
links_in_hover: true,
|
||||||
documentation: Some(HoverDocFormat::Markdown),
|
documentation: Some(HoverDocFormat::Markdown),
|
||||||
},
|
},
|
||||||
position,
|
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -576,7 +604,7 @@ mod tests {
|
||||||
links_in_hover: false,
|
links_in_hover: false,
|
||||||
documentation: Some(HoverDocFormat::Markdown),
|
documentation: Some(HoverDocFormat::Markdown),
|
||||||
},
|
},
|
||||||
position,
|
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -596,7 +624,7 @@ mod tests {
|
||||||
links_in_hover: true,
|
links_in_hover: true,
|
||||||
documentation: Some(HoverDocFormat::PlainText),
|
documentation: Some(HoverDocFormat::PlainText),
|
||||||
},
|
},
|
||||||
position,
|
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -616,13 +644,42 @@ mod tests {
|
||||||
links_in_hover: true,
|
links_in_hover: true,
|
||||||
documentation: Some(HoverDocFormat::Markdown),
|
documentation: Some(HoverDocFormat::Markdown),
|
||||||
},
|
},
|
||||||
position,
|
FileRange { file_id: position.file_id, range: TextRange::empty(position.offset) },
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
expect.assert_debug_eq(&hover.info.actions)
|
expect.assert_debug_eq(&hover.info.actions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_hover_range(ra_fixture: &str, expect: Expect) {
|
||||||
|
let (analysis, range) = fixture::range(ra_fixture);
|
||||||
|
let hover = analysis
|
||||||
|
.hover(
|
||||||
|
&HoverConfig {
|
||||||
|
links_in_hover: false,
|
||||||
|
documentation: Some(HoverDocFormat::Markdown),
|
||||||
|
},
|
||||||
|
range,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
expect.assert_eq(hover.info.markup.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_hover_range_no_results(ra_fixture: &str) {
|
||||||
|
let (analysis, range) = fixture::range(ra_fixture);
|
||||||
|
let hover = analysis
|
||||||
|
.hover(
|
||||||
|
&HoverConfig {
|
||||||
|
links_in_hover: false,
|
||||||
|
documentation: Some(HoverDocFormat::Markdown),
|
||||||
|
},
|
||||||
|
range,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(hover.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_shows_type_of_an_expression() {
|
fn hover_shows_type_of_an_expression() {
|
||||||
check(
|
check(
|
||||||
|
@ -3882,4 +3939,142 @@ struct Foo;
|
||||||
"#]],
|
"#]],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_range_math() {
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
fn f() { let expr = $01 + 2 * 3$0 }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```rust
|
||||||
|
i32
|
||||||
|
```"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
fn f() { let expr = 1 $0+ 2 * $03 }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```rust
|
||||||
|
i32
|
||||||
|
```"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
fn f() { let expr = 1 + $02 * 3$0 }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```rust
|
||||||
|
i32
|
||||||
|
```"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_range_arrays() {
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
fn f() { let expr = $0[1, 2, 3, 4]$0 }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```rust
|
||||||
|
[i32; 4]
|
||||||
|
```"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
fn f() { let expr = [1, 2, $03, 4]$0 }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```rust
|
||||||
|
[i32; 4]
|
||||||
|
```"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
fn f() { let expr = [1, 2, $03$0, 4] }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```rust
|
||||||
|
i32
|
||||||
|
```"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_range_functions() {
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
fn f<T>(a: &[T]) { }
|
||||||
|
fn b() { $0f$0(&[1, 2, 3, 4, 5]); }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```rust
|
||||||
|
fn f<i32>(&[i32])
|
||||||
|
```"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
fn f<T>(a: &[T]) { }
|
||||||
|
fn b() { f($0&[1, 2, 3, 4, 5]$0); }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```rust
|
||||||
|
&[i32; 5]
|
||||||
|
```"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_range_shows_nothing_when_invalid() {
|
||||||
|
check_hover_range_no_results(
|
||||||
|
r#"
|
||||||
|
fn f<T>(a: &[T]) { }
|
||||||
|
fn b()$0 { f(&[1, 2, 3, 4, 5]); }$0
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
check_hover_range_no_results(
|
||||||
|
r#"
|
||||||
|
fn f<T>$0(a: &[T]) { }
|
||||||
|
fn b() { f(&[1, 2, 3,$0 4, 5]); }
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
check_hover_range_no_results(
|
||||||
|
r#"
|
||||||
|
fn $0f() { let expr = [1, 2, 3, 4]$0 }
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_range_shows_unit_for_statements() {
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
fn f<T>(a: &[T]) { }
|
||||||
|
fn b() { $0f(&[1, 2, 3, 4, 5]); }$0
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```rust
|
||||||
|
()
|
||||||
|
```"#]],
|
||||||
|
);
|
||||||
|
|
||||||
|
check_hover_range(
|
||||||
|
r#"
|
||||||
|
fn f() { let expr$0 = $0[1, 2, 3, 4] }
|
||||||
|
"#,
|
||||||
|
expect![[r#"
|
||||||
|
```rust
|
||||||
|
()
|
||||||
|
```"#]],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -418,9 +418,9 @@ impl Analysis {
|
||||||
pub fn hover(
|
pub fn hover(
|
||||||
&self,
|
&self,
|
||||||
config: &HoverConfig,
|
config: &HoverConfig,
|
||||||
position: FilePosition,
|
range: FileRange,
|
||||||
) -> Cancellable<Option<RangeInfo<HoverResult>>> {
|
) -> Cancellable<Option<RangeInfo<HoverResult>>> {
|
||||||
self.with_db(|db| hover::hover(db, position, config))
|
self.with_db(|db| hover::hover(db, range, config))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return URL(s) for the documentation of the symbol under the cursor.
|
/// Return URL(s) for the documentation of the symbol under the cursor.
|
||||||
|
|
|
@ -118,6 +118,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
|
||||||
"ssr": true,
|
"ssr": true,
|
||||||
"onEnter": true,
|
"onEnter": true,
|
||||||
"parentModule": true,
|
"parentModule": true,
|
||||||
|
"hoverRange": true,
|
||||||
"runnables": {
|
"runnables": {
|
||||||
"kinds": [ "cargo" ],
|
"kinds": [ "cargo" ],
|
||||||
},
|
},
|
||||||
|
|
|
@ -36,7 +36,10 @@ use crate::{
|
||||||
from_proto,
|
from_proto,
|
||||||
global_state::{GlobalState, GlobalStateSnapshot},
|
global_state::{GlobalState, GlobalStateSnapshot},
|
||||||
line_index::LineEndings,
|
line_index::LineEndings,
|
||||||
lsp_ext::{self, InlayHint, InlayHintsParams, ViewCrateGraphParams, WorkspaceSymbolParams},
|
lsp_ext::{
|
||||||
|
self, InlayHint, InlayHintsParams, PositionOrRange, ViewCrateGraphParams,
|
||||||
|
WorkspaceSymbolParams,
|
||||||
|
},
|
||||||
lsp_utils::all_edits_are_disjoint,
|
lsp_utils::all_edits_are_disjoint,
|
||||||
to_proto, LspError, Result,
|
to_proto, LspError, Result,
|
||||||
};
|
};
|
||||||
|
@ -867,15 +870,21 @@ pub(crate) fn handle_signature_help(
|
||||||
|
|
||||||
pub(crate) fn handle_hover(
|
pub(crate) fn handle_hover(
|
||||||
snap: GlobalStateSnapshot,
|
snap: GlobalStateSnapshot,
|
||||||
params: lsp_types::HoverParams,
|
params: lsp_ext::HoverParams,
|
||||||
) -> Result<Option<lsp_ext::Hover>> {
|
) -> Result<Option<lsp_ext::Hover>> {
|
||||||
let _p = profile::span("handle_hover");
|
let _p = profile::span("handle_hover");
|
||||||
let position = from_proto::file_position(&snap, params.text_document_position_params)?;
|
let range = match params.position {
|
||||||
let info = match snap.analysis.hover(&snap.config.hover(), position)? {
|
PositionOrRange::Position(position) => Range::new(position, position),
|
||||||
|
PositionOrRange::Range(range) => range,
|
||||||
|
};
|
||||||
|
|
||||||
|
let file_range = from_proto::file_range(&snap, params.text_document, range)?;
|
||||||
|
let info = match snap.analysis.hover(&snap.config.hover(), file_range)? {
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
Some(info) => info,
|
Some(info) => info,
|
||||||
};
|
};
|
||||||
let line_index = snap.file_line_index(position.file_id)?;
|
|
||||||
|
let line_index = snap.file_line_index(file_range.file_id)?;
|
||||||
let range = to_proto::range(&line_index, info.range);
|
let range = to_proto::range(&line_index, info.range);
|
||||||
let hover = lsp_ext::Hover {
|
let hover = lsp_ext::Hover {
|
||||||
hover: lsp_types::Hover {
|
hover: lsp_types::Hover {
|
||||||
|
|
|
@ -376,11 +376,28 @@ pub struct SnippetTextEdit {
|
||||||
pub enum HoverRequest {}
|
pub enum HoverRequest {}
|
||||||
|
|
||||||
impl Request for HoverRequest {
|
impl Request for HoverRequest {
|
||||||
type Params = lsp_types::HoverParams;
|
type Params = HoverParams;
|
||||||
type Result = Option<Hover>;
|
type Result = Option<Hover>;
|
||||||
const METHOD: &'static str = "textDocument/hover";
|
const METHOD: &'static str = "textDocument/hover";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct HoverParams {
|
||||||
|
pub text_document: TextDocumentIdentifier,
|
||||||
|
pub position: PositionOrRange,
|
||||||
|
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub work_done_progress_params: WorkDoneProgressParams,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum PositionOrRange {
|
||||||
|
Position(lsp_types::Position),
|
||||||
|
Range(lsp_types::Range),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)]
|
||||||
pub struct Hover {
|
pub struct Hover {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
|
|
|
@ -173,7 +173,7 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: this is horrible inefficient. I bet there's a cool algorithm to diff trees properly.
|
// FIXME: this is horribly inefficient. I bet there's a cool algorithm to diff trees properly.
|
||||||
fn go(diff: &mut TreeDiff, lhs: SyntaxElement, rhs: SyntaxElement) {
|
fn go(diff: &mut TreeDiff, lhs: SyntaxElement, rhs: SyntaxElement) {
|
||||||
let (lhs, rhs) = match lhs.as_node().zip(rhs.as_node()) {
|
let (lhs, rhs) = match lhs.as_node().zip(rhs.as_node()) {
|
||||||
Some((lhs, rhs)) => (lhs, rhs),
|
Some((lhs, rhs)) => (lhs, rhs),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!---
|
<!---
|
||||||
lsp_ext.rs hash: 3b2931972b33198b
|
lsp_ext.rs hash: 5f96a69eb3a5ebc3
|
||||||
|
|
||||||
If you need to change the above hash to make the test pass, please check if you
|
If you need to change the above hash to make the test pass, please check if you
|
||||||
need to adjust this doc as well and ping this issue:
|
need to adjust this doc as well and ping this issue:
|
||||||
|
@ -13,7 +13,7 @@ need to adjust this doc as well and ping this issue:
|
||||||
This document describes LSP extensions used by rust-analyzer.
|
This document describes LSP extensions used by rust-analyzer.
|
||||||
It's a best effort document, when in doubt, consult the source (and send a PR with clarification ;-) ).
|
It's a best effort document, when in doubt, consult the source (and send a PR with clarification ;-) ).
|
||||||
We aim to upstream all non Rust-specific extensions to the protocol, but this is not a top priority.
|
We aim to upstream all non Rust-specific extensions to the protocol, but this is not a top priority.
|
||||||
All capabilities are enabled via `experimental` field of `ClientCapabilities` or `ServerCapabilities`.
|
All capabilities are enabled via the `experimental` field of `ClientCapabilities` or `ServerCapabilities`.
|
||||||
Requests which we hope to upstream live under `experimental/` namespace.
|
Requests which we hope to upstream live under `experimental/` namespace.
|
||||||
Requests, which are likely to always remain specific to `rust-analyzer` are under `rust-analyzer/` namespace.
|
Requests, which are likely to always remain specific to `rust-analyzer` are under `rust-analyzer/` namespace.
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ https://clangd.llvm.org/extensions.html#utf-8-offsets
|
||||||
|
|
||||||
**Issue:** https://github.com/microsoft/language-server-protocol/issues/567
|
**Issue:** https://github.com/microsoft/language-server-protocol/issues/567
|
||||||
|
|
||||||
The `initializationOptions` filed of the `InitializeParams` of the initialization request should contain `"rust-analyzer"` section of the configuration.
|
The `initializationOptions` field of the `InitializeParams` of the initialization request should contain the `"rust-analyzer"` section of the configuration.
|
||||||
|
|
||||||
`rust-analyzer` normally sends a `"workspace/configuration"` request with `{ "items": ["rust-analyzer"] }` payload.
|
`rust-analyzer` normally sends a `"workspace/configuration"` request with `{ "items": ["rust-analyzer"] }` payload.
|
||||||
However, the server can't do this during initialization.
|
However, the server can't do this during initialization.
|
||||||
|
@ -81,7 +81,7 @@ At the moment, rust-analyzer guarantees that only a single edit will have `Inser
|
||||||
|
|
||||||
**Experimental Client Capability:** `{ "codeActionGroup": boolean }`
|
**Experimental Client Capability:** `{ "codeActionGroup": boolean }`
|
||||||
|
|
||||||
If this capability is set, `CodeAction` returned from the server contain an additional field, `group`:
|
If this capability is set, `CodeAction`s returned from the server contain an additional field, `group`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
interface CodeAction {
|
interface CodeAction {
|
||||||
|
@ -209,7 +209,7 @@ fn main() {
|
||||||
|
|
||||||
**Experimental Server Capability:** `{ "onEnter": boolean }`
|
**Experimental Server Capability:** `{ "onEnter": boolean }`
|
||||||
|
|
||||||
This request is sent from client to server to handle <kbd>Enter</kbd> keypress.
|
This request is sent from client to server to handle the <kbd>Enter</kbd> key press.
|
||||||
|
|
||||||
**Method:** `experimental/onEnter`
|
**Method:** `experimental/onEnter`
|
||||||
|
|
||||||
|
@ -658,6 +658,33 @@ interface TestInfo {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Hover Range
|
||||||
|
|
||||||
|
**Issue:** https://github.com/microsoft/language-server-protocol/issues/377
|
||||||
|
|
||||||
|
**Experimental Server Capability:** { "hoverRange": boolean }
|
||||||
|
|
||||||
|
This request build upon the current `textDocument/hover` to show the type of the expression currently selected.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface HoverParams extends lc.WorkDoneProgressParams {
|
||||||
|
textDocument: lc.TextDocumentIdentifier;
|
||||||
|
position: lc.Range | lc.Position;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Whenever the client sends a `Range`, it is understood as the current selection and any hover included in the range will show the type of the expression if possible.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let expression = $01 + 2 * 3$0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Triggering a hover inside the selection above will show a result of `i32`.
|
||||||
|
|
||||||
## Move Item
|
## Move Item
|
||||||
|
|
||||||
**Issue:** https://github.com/rust-analyzer/rust-analyzer/issues/6823
|
**Issue:** https://github.com/rust-analyzer/rust-analyzer/issues/6823
|
||||||
|
|
|
@ -56,9 +56,15 @@ export function createClient(serverPath: string, workspace: Workspace, extraEnv:
|
||||||
traceOutputChannel,
|
traceOutputChannel,
|
||||||
middleware: {
|
middleware: {
|
||||||
async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _next: lc.ProvideHoverSignature) {
|
async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _next: lc.ProvideHoverSignature) {
|
||||||
return client.sendRequest(lc.HoverRequest.type, client.code2ProtocolConverter.asTextDocumentPositionParams(document, position), token).then(
|
const editor = vscode.window.activeTextEditor;
|
||||||
|
const positionOrRange = editor?.selection?.contains(position) ? client.code2ProtocolConverter.asRange(editor.selection) : client.code2ProtocolConverter.asPosition(position);
|
||||||
|
return client.sendRequest(ra.hover, {
|
||||||
|
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
|
||||||
|
position: positionOrRange
|
||||||
|
}, token).then(
|
||||||
(result) => {
|
(result) => {
|
||||||
const hover = client.protocol2CodeConverter.asHover(result);
|
const hover =
|
||||||
|
client.protocol2CodeConverter.asHover(result);
|
||||||
if (hover) {
|
if (hover) {
|
||||||
const actions = (<any>result).actions;
|
const actions = (<any>result).actions;
|
||||||
if (actions) {
|
if (actions) {
|
||||||
|
@ -68,9 +74,15 @@ export function createClient(serverPath: string, workspace: Workspace, extraEnv:
|
||||||
return hover;
|
return hover;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
client.handleFailedRequest(lc.HoverRequest.type, token, error, null);
|
client.handleFailedRequest(
|
||||||
|
lc.HoverRequest.type,
|
||||||
|
token,
|
||||||
|
error,
|
||||||
|
null
|
||||||
|
);
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
// Using custom handling of CodeActions to support action groups and snippet edits.
|
// Using custom handling of CodeActions to support action groups and snippet edits.
|
||||||
// Note that this means we have to re-implement lazy edit resolving ourselves as well.
|
// Note that this means we have to re-implement lazy edit resolving ourselves as well.
|
||||||
|
|
|
@ -19,6 +19,13 @@ export const serverStatus = new lc.NotificationType<ServerStatusParams>("experim
|
||||||
|
|
||||||
export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace");
|
export const reloadWorkspace = new lc.RequestType0<null, void>("rust-analyzer/reloadWorkspace");
|
||||||
|
|
||||||
|
export const hover = new lc.RequestType<HoverParams, lc.Hover | null, void>("textDocument/hover");
|
||||||
|
|
||||||
|
export interface HoverParams extends lc.WorkDoneProgressParams {
|
||||||
|
textDocument: lc.TextDocumentIdentifier;
|
||||||
|
position: lc.Range | lc.Position;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SyntaxTreeParams {
|
export interface SyntaxTreeParams {
|
||||||
textDocument: lc.TextDocumentIdentifier;
|
textDocument: lc.TextDocumentIdentifier;
|
||||||
range: lc.Range | null;
|
range: lc.Range | null;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue