Moved CodeLens to ide crate

This commit is contained in:
ivan770 2021-02-13 13:07:47 +02:00
parent 935830d05b
commit 185da286d2
No known key found for this signature in database
GPG key ID: D8C4BD5AE4D9CC4D
7 changed files with 388 additions and 202 deletions

View file

@ -0,0 +1,142 @@
use hir::Semantics;
use ide_db::{
base_db::{FileId, FilePosition, FileRange, SourceDatabase},
RootDatabase, SymbolKind,
};
use syntax::TextRange;
use crate::{
file_structure::file_structure,
fn_references::find_all_methods,
goto_implementation::goto_implementation,
references::find_all_refs,
runnables::{runnables, Runnable},
NavigationTarget, RunnableKind,
};
// Feature: Annotations
//
// Provides user with annotations above items for looking up references or impl blocks
// and running/debugging binaries.
pub struct Annotation {
pub range: TextRange,
pub kind: AnnotationKind,
}
pub enum AnnotationKind {
Runnable { debug: bool, runnable: Runnable },
HasImpls { position: FilePosition, data: Option<Vec<NavigationTarget>> },
HasReferences { position: FilePosition, data: Option<Vec<FileRange>> },
}
pub struct AnnotationConfig {
pub binary_target: bool,
pub annotate_runnables: bool,
pub annotate_impls: bool,
pub annotate_references: bool,
pub annotate_method_references: bool,
pub run: bool,
pub debug: bool,
}
pub(crate) fn annotations(
db: &RootDatabase,
file_id: FileId,
config: AnnotationConfig,
) -> Vec<Annotation> {
let mut annotations = Vec::default();
if config.annotate_runnables {
for runnable in runnables(db, file_id) {
if !matches!(runnable.kind, RunnableKind::Bin) || !config.binary_target {
continue;
}
let action = runnable.action();
let range = runnable.nav.full_range;
if config.run {
annotations.push(Annotation {
range,
// FIXME: This one allocates without reason if run is enabled, but debug is disabled
kind: AnnotationKind::Runnable { debug: false, runnable: runnable.clone() },
});
}
if action.debugee && config.debug {
annotations.push(Annotation {
range,
kind: AnnotationKind::Runnable { debug: true, runnable },
});
}
}
}
file_structure(&db.parse(file_id).tree())
.into_iter()
.filter(|node| {
matches!(
node.kind,
SymbolKind::Trait
| SymbolKind::Struct
| SymbolKind::Enum
| SymbolKind::Union
| SymbolKind::Const
)
})
.for_each(|node| {
if config.annotate_impls && node.kind != SymbolKind::Const {
annotations.push(Annotation {
range: node.node_range,
kind: AnnotationKind::HasImpls {
position: FilePosition { file_id, offset: node.navigation_range.start() },
data: None,
},
});
}
if config.annotate_references {
annotations.push(Annotation {
range: node.node_range,
kind: AnnotationKind::HasReferences {
position: FilePosition { file_id, offset: node.navigation_range.start() },
data: None,
},
});
}
});
if config.annotate_method_references {
annotations.extend(find_all_methods(db, file_id).into_iter().map(|method| Annotation {
range: method.range,
kind: AnnotationKind::HasReferences {
position: FilePosition { file_id, offset: method.range.start() },
data: None,
},
}));
}
annotations
}
pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation {
match annotation.kind {
AnnotationKind::HasImpls { position, ref mut data } => {
*data = goto_implementation(db, position).map(|range| range.info);
}
AnnotationKind::HasReferences { position, ref mut data } => {
*data = find_all_refs(&Semantics::new(db), position, None).map(|result| {
result
.references
.into_iter()
.map(|(_, access)| access.into_iter())
.flatten()
.map(|(range, _)| FileRange { file_id: position.file_id, range })
.collect()
});
}
_ => {}
};
annotation
}

View file

@ -22,6 +22,7 @@ mod markup;
mod prime_caches; mod prime_caches;
mod display; mod display;
mod annotations;
mod call_hierarchy; mod call_hierarchy;
mod diagnostics; mod diagnostics;
mod expand_macro; mod expand_macro;
@ -63,6 +64,7 @@ use syntax::SourceFile;
use crate::display::ToNav; use crate::display::ToNav;
pub use crate::{ pub use crate::{
annotations::{Annotation, AnnotationConfig, AnnotationKind},
call_hierarchy::CallItem, call_hierarchy::CallItem,
diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity}, diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity},
display::navigation_target::NavigationTarget, display::navigation_target::NavigationTarget,
@ -555,6 +557,18 @@ impl Analysis {
}) })
} }
pub fn annotations(
&self,
file_id: FileId,
config: AnnotationConfig,
) -> Cancelable<Vec<Annotation>> {
self.with_db(|db| annotations::annotations(db, file_id, config))
}
pub fn resolve_annotation(&self, annotation: Annotation) -> Cancelable<Annotation> {
self.with_db(|db| annotations::resolve_annotation(db, annotation))
}
/// Performs an operation on that may be Canceled. /// Performs an operation on that may be Canceled.
fn with_db<F, T>(&self, f: F) -> Cancelable<T> fn with_db<F, T>(&self, f: F) -> Cancelable<T>
where where

View file

@ -1,12 +1,12 @@
//! Conversion lsp_types types to rust-analyzer specific ones. //! Conversion lsp_types types to rust-analyzer specific ones.
use std::convert::TryFrom; use std::convert::TryFrom;
use ide::{AssistKind, LineCol, LineIndex}; use ide::{Annotation, AnnotationKind, AssistKind, LineCol, LineIndex};
use ide_db::base_db::{FileId, FilePosition, FileRange}; use ide_db::base_db::{FileId, FilePosition, FileRange};
use syntax::{TextRange, TextSize}; use syntax::{TextRange, TextSize};
use vfs::AbsPathBuf; use vfs::AbsPathBuf;
use crate::{global_state::GlobalStateSnapshot, Result}; use crate::{from_json, global_state::GlobalStateSnapshot, lsp_ext, Result};
pub(crate) fn abs_path(url: &lsp_types::Url) -> Result<AbsPathBuf> { pub(crate) fn abs_path(url: &lsp_types::Url) -> Result<AbsPathBuf> {
let path = url.to_file_path().map_err(|()| "url is not a file")?; let path = url.to_file_path().map_err(|()| "url is not a file")?;
@ -66,3 +66,39 @@ pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind>
Some(assist_kind) Some(assist_kind)
} }
pub(crate) fn annotation(
world: &GlobalStateSnapshot,
code_lens: lsp_types::CodeLens,
) -> Result<Annotation> {
let data = code_lens.data.unwrap();
let resolve = from_json::<lsp_ext::CodeLensResolveData>("CodeLensResolveData", data)?;
match resolve {
lsp_ext::CodeLensResolveData::Impls(params) => {
let file_id =
world.url_to_file_id(&params.text_document_position_params.text_document.uri)?;
let line_index = world.analysis.file_line_index(file_id)?;
Ok(Annotation {
range: text_range(&line_index, code_lens.range),
kind: AnnotationKind::HasImpls {
position: file_position(world, params.text_document_position_params)?,
data: None,
},
})
}
lsp_ext::CodeLensResolveData::References(params) => {
let file_id = world.url_to_file_id(&params.text_document.uri)?;
let line_index = world.analysis.file_line_index(file_id)?;
Ok(Annotation {
range: text_range(&line_index, code_lens.range),
kind: AnnotationKind::HasReferences {
position: file_position(world, params)?,
data: None,
},
})
}
}
}

View file

@ -9,8 +9,9 @@ use std::{
}; };
use ide::{ use ide::{
FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, LineIndex, NavigationTarget, AnnotationConfig, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, LineIndex,
Query, RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange, TextEdit, NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange,
TextEdit,
}; };
use ide_db::SymbolKind; use ide_db::SymbolKind;
use itertools::Itertools; use itertools::Itertools;
@ -35,7 +36,7 @@ use crate::{
cargo_target_spec::CargoTargetSpec, cargo_target_spec::CargoTargetSpec,
config::RustfmtConfig, config::RustfmtConfig,
diff::diff, diff::diff,
from_json, from_proto, from_proto,
global_state::{GlobalState, GlobalStateSnapshot}, global_state::{GlobalState, GlobalStateSnapshot},
line_endings::LineEndings, line_endings::LineEndings,
lsp_ext::{self, InlayHint, InlayHintsParams}, lsp_ext::{self, InlayHint, InlayHintsParams},
@ -1078,177 +1079,51 @@ pub(crate) fn handle_code_lens(
params: lsp_types::CodeLensParams, params: lsp_types::CodeLensParams,
) -> Result<Option<Vec<CodeLens>>> { ) -> Result<Option<Vec<CodeLens>>> {
let _p = profile::span("handle_code_lens"); let _p = profile::span("handle_code_lens");
let mut lenses: Vec<CodeLens> = Default::default();
let lens_config = snap.config.lens(); let lens_config = snap.config.lens();
if lens_config.none() { if lens_config.none() {
// early return before any db query! // early return before any db query!
return Ok(Some(lenses)); return Ok(Some(Vec::default()));
} }
let file_id = from_proto::file_id(&snap, &params.text_document.uri)?; let file_id = from_proto::file_id(&snap, &params.text_document.uri)?;
let line_index = snap.analysis.file_line_index(file_id)?; let cargo_target_spec = CargoTargetSpec::for_file(&snap, file_id)?;
let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
if lens_config.runnable() { let lenses = snap
// Gather runnables .analysis
for runnable in snap.analysis.runnables(file_id)? { .annotations(
if should_skip_target(&runnable, cargo_spec.as_ref()) { file_id,
continue; AnnotationConfig {
} binary_target: cargo_target_spec
.map(|spec| {
let action = runnable.action(); matches!(
let range = to_proto::range(&line_index, runnable.nav.full_range); spec.target_kind,
let r = to_proto::runnable(&snap, file_id, runnable)?; TargetKind::Bin | TargetKind::Example | TargetKind::Test
if lens_config.run { )
let lens = CodeLens {
range,
command: Some(run_single_command(&r, action.run_title)),
data: None,
};
lenses.push(lens);
}
if action.debugee && lens_config.debug {
let debug_lens =
CodeLens { range, command: Some(debug_single_command(&r)), data: None };
lenses.push(debug_lens);
}
}
}
if lens_config.implementations || lens_config.refs {
snap.analysis
.file_structure(file_id)?
.into_iter()
.filter(|it| {
matches!(
it.kind,
SymbolKind::Trait | SymbolKind::Struct | SymbolKind::Enum | SymbolKind::Union
)
})
.for_each(|it| {
let range = to_proto::range(&line_index, it.node_range);
let position = to_proto::position(&line_index, it.navigation_range.start());
let doc_pos = lsp_types::TextDocumentPositionParams::new(
params.text_document.clone(),
position,
);
let goto_params = lsp_types::request::GotoImplementationParams {
text_document_position_params: doc_pos.clone(),
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
};
if lens_config.implementations {
lenses.push(CodeLens {
range,
command: None,
data: Some(to_value(CodeLensResolveData::Impls(goto_params)).unwrap()),
}) })
} .unwrap_or(false),
annotate_runnables: lens_config.runnable(),
if lens_config.refs { annotate_impls: lens_config.implementations,
lenses.push(CodeLens { annotate_references: lens_config.refs,
range, annotate_method_references: lens_config.method_refs,
command: None, run: lens_config.run,
data: Some(to_value(CodeLensResolveData::References(doc_pos)).unwrap()), debug: lens_config.debug,
}) },
} )?
}); .into_iter()
} .map(|annotation| to_proto::code_lens(&snap, annotation).unwrap())
.collect();
if lens_config.method_refs {
lenses.extend(snap.analysis.find_all_methods(file_id)?.into_iter().map(|it| {
let range = to_proto::range(&line_index, it.range);
let position = to_proto::position(&line_index, it.range.start());
let lens_params =
lsp_types::TextDocumentPositionParams::new(params.text_document.clone(), position);
CodeLens {
range,
command: None,
data: Some(to_value(CodeLensResolveData::References(lens_params)).unwrap()),
}
}));
}
Ok(Some(lenses)) Ok(Some(lenses))
} }
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum CodeLensResolveData {
Impls(lsp_types::request::GotoImplementationParams),
References(lsp_types::TextDocumentPositionParams),
}
pub(crate) fn handle_code_lens_resolve( pub(crate) fn handle_code_lens_resolve(
snap: GlobalStateSnapshot, snap: GlobalStateSnapshot,
code_lens: CodeLens, code_lens: CodeLens,
) -> Result<CodeLens> { ) -> Result<CodeLens> {
let _p = profile::span("handle_code_lens_resolve"); let annotation = from_proto::annotation(&snap, code_lens)?;
let data = code_lens.data.unwrap();
let resolve = from_json::<Option<CodeLensResolveData>>("CodeLensResolveData", data)?;
match resolve {
Some(CodeLensResolveData::Impls(lens_params)) => {
let locations: Vec<Location> =
match handle_goto_implementation(snap, lens_params.clone())? {
Some(lsp_types::GotoDefinitionResponse::Scalar(loc)) => vec![loc],
Some(lsp_types::GotoDefinitionResponse::Array(locs)) => locs,
Some(lsp_types::GotoDefinitionResponse::Link(links)) => links
.into_iter()
.map(|link| Location::new(link.target_uri, link.target_selection_range))
.collect(),
_ => vec![],
};
let title = implementation_title(locations.len()); Ok(to_proto::code_lens(&snap, snap.analysis.resolve_annotation(annotation)?)?)
let cmd = show_references_command(
title,
&lens_params.text_document_position_params.text_document.uri,
code_lens.range.start,
locations,
);
Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
}
Some(CodeLensResolveData::References(doc_position)) => {
let position = from_proto::file_position(&snap, doc_position.clone())?;
let locations = snap
.analysis
.find_all_refs(position, None)
.unwrap_or(None)
.map(|r| {
r.references
.into_iter()
.flat_map(|(file_id, ranges)| {
ranges.into_iter().map(move |(range, _)| FileRange { file_id, range })
})
.filter_map(|frange| to_proto::location(&snap, frange).ok())
.collect_vec()
})
.unwrap_or_default();
let title = reference_title(locations.len());
let cmd = if locations.is_empty() {
Command { title, command: "".into(), arguments: None }
} else {
show_references_command(
title,
&doc_position.text_document.uri,
code_lens.range.start,
locations,
)
};
Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None })
}
None => Ok(CodeLens {
range: code_lens.range,
command: Some(Command { title: "Error".into(), ..Default::default() }),
data: None,
}),
}
} }
pub(crate) fn handle_document_highlight( pub(crate) fn handle_document_highlight(
@ -1547,43 +1422,6 @@ pub(crate) fn handle_open_cargo_toml(
Ok(Some(res)) Ok(Some(res))
} }
fn implementation_title(count: usize) -> String {
if count == 1 {
"1 implementation".into()
} else {
format!("{} implementations", count)
}
}
fn reference_title(count: usize) -> String {
if count == 1 {
"1 reference".into()
} else {
format!("{} references", count)
}
}
fn show_references_command(
title: String,
uri: &lsp_types::Url,
position: lsp_types::Position,
locations: Vec<lsp_types::Location>,
) -> Command {
// We cannot use the 'editor.action.showReferences' command directly
// because that command requires vscode types which we convert in the handler
// on the client side.
Command {
title,
command: "rust-analyzer.showReferences".into(),
arguments: Some(vec![
to_value(uri).unwrap(),
to_value(position).unwrap(),
to_value(locations).unwrap(),
]),
}
}
fn run_single_command(runnable: &lsp_ext::Runnable, title: &str) -> Command { fn run_single_command(runnable: &lsp_ext::Runnable, title: &str) -> Command {
Command { Command {
title: title.to_string(), title: title.to_string(),
@ -1635,8 +1473,8 @@ fn show_impl_command_link(
.into_iter() .into_iter()
.filter_map(|nav| to_proto::location_from_nav(snap, nav).ok()) .filter_map(|nav| to_proto::location_from_nav(snap, nav).ok())
.collect(); .collect();
let title = implementation_title(locations.len()); let title = to_proto::implementation_title(locations.len());
let command = show_references_command(title, &uri, position, locations); let command = to_proto::show_references_command(title, &uri, position, locations);
return Some(lsp_ext::CommandLinkGroup { return Some(lsp_ext::CommandLinkGroup {
commands: vec![to_command_link(command, "Go to implementations".into())], commands: vec![to_command_link(command, "Go to implementations".into())],

View file

@ -377,3 +377,11 @@ impl Request for OpenCargoToml {
pub struct OpenCargoTomlParams { pub struct OpenCargoTomlParams {
pub text_document: TextDocumentIdentifier, pub text_document: TextDocumentIdentifier,
} }
/// Information about CodeLens, that is to be resolved.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) enum CodeLensResolveData {
Impls(lsp_types::request::GotoImplementationParams),
References(lsp_types::TextDocumentPositionParams),
}

View file

@ -5,13 +5,15 @@ use std::{
}; };
use ide::{ use ide::{
Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, Documentation, FileId, Annotation, AnnotationKind, Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind,
FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct, HlRange, HlTag, Indel, Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct,
InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup, NavigationTarget, ReferenceAccess, HlRange, HlTag, Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup,
RenameError, Runnable, Severity, SourceChange, TextEdit, TextRange, TextSize, NavigationTarget, ReferenceAccess, RenameError, Runnable, Severity, SourceChange, TextEdit,
TextRange, TextSize,
}; };
use ide_db::SymbolKind; use ide_db::SymbolKind;
use itertools::Itertools; use itertools::Itertools;
use serde_json::to_value;
use crate::{ use crate::{
cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot, cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot,
@ -863,6 +865,141 @@ pub(crate) fn runnable(
}) })
} }
pub(crate) fn code_lens(
snap: &GlobalStateSnapshot,
annotation: Annotation,
) -> Result<lsp_types::CodeLens> {
match annotation.kind {
AnnotationKind::Runnable { debug, runnable: run } => {
let line_index = snap.analysis.file_line_index(run.nav.file_id)?;
let annotation_range = range(&line_index, annotation.range);
let action = run.action();
let r = runnable(&snap, run.nav.file_id, run)?;
let command = if debug {
lsp_types::Command {
title: action.run_title.to_string(),
command: "rust-analyzer.runSingle".into(),
arguments: Some(vec![to_value(r).unwrap()]),
}
} else {
lsp_types::Command {
title: "Debug".into(),
command: "rust-analyzer.debugSingle".into(),
arguments: Some(vec![to_value(r).unwrap()]),
}
};
Ok(lsp_types::CodeLens { range: annotation_range, command: Some(command), data: None })
}
AnnotationKind::HasImpls { position: file_position, data } => {
let line_index = snap.analysis.file_line_index(file_position.file_id)?;
let annotation_range = range(&line_index, annotation.range);
let url = url(snap, file_position.file_id);
let position = position(&line_index, file_position.offset);
let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
let doc_pos = lsp_types::TextDocumentPositionParams::new(id.clone(), position);
let goto_params = lsp_types::request::GotoImplementationParams {
text_document_position_params: doc_pos.clone(),
work_done_progress_params: Default::default(),
partial_result_params: Default::default(),
};
let command = data.map(|ranges| {
let locations: Vec<lsp_types::Location> = ranges
.into_iter()
.filter_map(|target| {
location(
snap,
FileRange { file_id: target.file_id, range: target.full_range },
)
.ok()
})
.collect();
show_references_command(
implementation_title(locations.len()),
&url,
position,
locations,
)
});
Ok(lsp_types::CodeLens {
range: annotation_range,
command,
data: Some(to_value(lsp_ext::CodeLensResolveData::Impls(goto_params)).unwrap()),
})
}
AnnotationKind::HasReferences { position: file_position, data } => {
let line_index = snap.analysis.file_line_index(file_position.file_id)?;
let annotation_range = range(&line_index, annotation.range);
let url = url(snap, file_position.file_id);
let position = position(&line_index, file_position.offset);
let id = lsp_types::TextDocumentIdentifier { uri: url.clone() };
let doc_pos = lsp_types::TextDocumentPositionParams::new(id, position);
let command = data.map(|ranges| {
let locations: Vec<lsp_types::Location> =
ranges.into_iter().filter_map(|range| location(snap, range).ok()).collect();
show_references_command(reference_title(locations.len()), &url, position, locations)
});
Ok(lsp_types::CodeLens {
range: annotation_range,
command,
data: Some(to_value(lsp_ext::CodeLensResolveData::References(doc_pos)).unwrap()),
})
}
}
}
pub(crate) fn show_references_command(
title: String,
uri: &lsp_types::Url,
position: lsp_types::Position,
locations: Vec<lsp_types::Location>,
) -> lsp_types::Command {
// We cannot use the 'editor.action.showReferences' command directly
// because that command requires vscode types which we convert in the handler
// on the client side.
lsp_types::Command {
title,
command: "rust-analyzer.showReferences".into(),
arguments: Some(vec![
to_value(uri).unwrap(),
to_value(position).unwrap(),
to_value(locations).unwrap(),
]),
}
}
pub(crate) fn implementation_title(count: usize) -> String {
if count == 1 {
"1 implementation".into()
} else {
format!("{} implementations", count)
}
}
pub(crate) fn reference_title(count: usize) -> String {
if count == 1 {
"1 reference".into()
} else {
format!("{} references", count)
}
}
pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent { pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent {
let value = crate::markdown::format_docs(markup.as_str()); let value = crate::markdown::format_docs(markup.as_str());
lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value } lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value }

View file

@ -1,5 +1,5 @@
<!--- <!---
lsp_ext.rs hash: 8f1ae8530f69e3a3 lsp_ext.rs hash: 34aec6bfeaeb97a
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:
@ -573,3 +573,14 @@ This request is sent from client to server to open the current project's Cargo.t
``` ```
`experimental/openCargoToml` returns a single `Link` to the start of the `[package]` keyword. `experimental/openCargoToml` returns a single `Link` to the start of the `[package]` keyword.
## CodeLens resolve request
This request is sent from client to server to resolve previously provided CodeLens.
As an alternative to `any` type in `data` field of `CodeLens`, you may use `CodeLensResolveData`:
```typescript
interface CodeLensResolveData {
data: (DefinitionParams | TextDocumentPositionParams),
}
```