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 display;
mod annotations;
mod call_hierarchy;
mod diagnostics;
mod expand_macro;
@ -63,6 +64,7 @@ use syntax::SourceFile;
use crate::display::ToNav;
pub use crate::{
annotations::{Annotation, AnnotationConfig, AnnotationKind},
call_hierarchy::CallItem,
diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity},
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.
fn with_db<F, T>(&self, f: F) -> Cancelable<T>
where

View file

@ -1,12 +1,12 @@
//! Conversion lsp_types types to rust-analyzer specific ones.
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 syntax::{TextRange, TextSize};
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> {
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)
}
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::{
FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, LineIndex, NavigationTarget,
Query, RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange, TextEdit,
AnnotationConfig, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, LineIndex,
NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange,
TextEdit,
};
use ide_db::SymbolKind;
use itertools::Itertools;
@ -35,7 +36,7 @@ use crate::{
cargo_target_spec::CargoTargetSpec,
config::RustfmtConfig,
diff::diff,
from_json, from_proto,
from_proto,
global_state::{GlobalState, GlobalStateSnapshot},
line_endings::LineEndings,
lsp_ext::{self, InlayHint, InlayHintsParams},
@ -1078,177 +1079,51 @@ pub(crate) fn handle_code_lens(
params: lsp_types::CodeLensParams,
) -> Result<Option<Vec<CodeLens>>> {
let _p = profile::span("handle_code_lens");
let mut lenses: Vec<CodeLens> = Default::default();
let lens_config = snap.config.lens();
if lens_config.none() {
// 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 line_index = snap.analysis.file_line_index(file_id)?;
let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?;
let cargo_target_spec = CargoTargetSpec::for_file(&snap, file_id)?;
if lens_config.runnable() {
// Gather runnables
for runnable in snap.analysis.runnables(file_id)? {
if should_skip_target(&runnable, cargo_spec.as_ref()) {
continue;
}
let action = runnable.action();
let range = to_proto::range(&line_index, runnable.nav.full_range);
let r = to_proto::runnable(&snap, file_id, runnable)?;
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()),
let lenses = snap
.analysis
.annotations(
file_id,
AnnotationConfig {
binary_target: cargo_target_spec
.map(|spec| {
matches!(
spec.target_kind,
TargetKind::Bin | TargetKind::Example | TargetKind::Test
)
})
}
if lens_config.refs {
lenses.push(CodeLens {
range,
command: None,
data: Some(to_value(CodeLensResolveData::References(doc_pos)).unwrap()),
})
}
});
}
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()),
}
}));
}
.unwrap_or(false),
annotate_runnables: lens_config.runnable(),
annotate_impls: lens_config.implementations,
annotate_references: lens_config.refs,
annotate_method_references: lens_config.method_refs,
run: lens_config.run,
debug: lens_config.debug,
},
)?
.into_iter()
.map(|annotation| to_proto::code_lens(&snap, annotation).unwrap())
.collect();
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(
snap: GlobalStateSnapshot,
code_lens: CodeLens,
) -> Result<CodeLens> {
let _p = profile::span("handle_code_lens_resolve");
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 annotation = from_proto::annotation(&snap, code_lens)?;
let title = implementation_title(locations.len());
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,
}),
}
Ok(to_proto::code_lens(&snap, snap.analysis.resolve_annotation(annotation)?)?)
}
pub(crate) fn handle_document_highlight(
@ -1547,43 +1422,6 @@ pub(crate) fn handle_open_cargo_toml(
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 {
Command {
title: title.to_string(),
@ -1635,8 +1473,8 @@ fn show_impl_command_link(
.into_iter()
.filter_map(|nav| to_proto::location_from_nav(snap, nav).ok())
.collect();
let title = implementation_title(locations.len());
let command = show_references_command(title, &uri, position, locations);
let title = to_proto::implementation_title(locations.len());
let command = to_proto::show_references_command(title, &uri, position, locations);
return Some(lsp_ext::CommandLinkGroup {
commands: vec![to_command_link(command, "Go to implementations".into())],

View file

@ -377,3 +377,11 @@ impl Request for OpenCargoToml {
pub struct OpenCargoTomlParams {
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::{
Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, Documentation, FileId,
FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct, HlRange, HlTag, Indel,
InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup, NavigationTarget, ReferenceAccess,
RenameError, Runnable, Severity, SourceChange, TextEdit, TextRange, TextSize,
Annotation, AnnotationKind, Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind,
Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct,
HlRange, HlTag, Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup,
NavigationTarget, ReferenceAccess, RenameError, Runnable, Severity, SourceChange, TextEdit,
TextRange, TextSize,
};
use ide_db::SymbolKind;
use itertools::Itertools;
use serde_json::to_value;
use crate::{
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 {
let value = crate::markdown::format_docs(markup.as_str());
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
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.
## 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),
}
```