From 185da286d26ea7f892097c48b79a28acd7e5f172 Mon Sep 17 00:00:00 2001 From: ivan770 Date: Sat, 13 Feb 2021 13:07:47 +0200 Subject: Moved CodeLens to ide crate --- crates/ide/src/annotations.rs | 142 ++++++++++++++++++++ crates/ide/src/lib.rs | 14 ++ crates/rust-analyzer/src/from_proto.rs | 40 +++++- crates/rust-analyzer/src/handlers.rs | 228 +++++---------------------------- crates/rust-analyzer/src/lsp_ext.rs | 8 ++ crates/rust-analyzer/src/to_proto.rs | 145 ++++++++++++++++++++- docs/dev/lsp-extensions.md | 13 +- 7 files changed, 388 insertions(+), 202 deletions(-) create mode 100644 crates/ide/src/annotations.rs diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs new file mode 100644 index 000000000..6d54b6b57 --- /dev/null +++ b/crates/ide/src/annotations.rs @@ -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> }, + HasReferences { position: FilePosition, data: Option> }, +} + +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 { + 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 +} diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 592b12925..89e7bef7d 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -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> { + self.with_db(|db| annotations::annotations(db, file_id, config)) + } + + pub fn resolve_annotation(&self, annotation: Annotation) -> Cancelable { + self.with_db(|db| annotations::resolve_annotation(db, annotation)) + } + /// Performs an operation on that may be Canceled. fn with_db(&self, f: F) -> Cancelable where diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs index aa6b808d6..6676eebf4 100644 --- a/crates/rust-analyzer/src/from_proto.rs +++ b/crates/rust-analyzer/src/from_proto.rs @@ -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 { 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 Some(assist_kind) } + +pub(crate) fn annotation( + world: &GlobalStateSnapshot, + code_lens: lsp_types::CodeLens, +) -> Result { + let data = code_lens.data.unwrap(); + let resolve = from_json::("CodeLensResolveData", data)?; + + match resolve { + lsp_ext::CodeLensResolveData::Impls(params) => { + let file_id = + world.url_to_file_id(¶ms.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(¶ms.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, + }, + }) + } + } +} diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 8898c12e3..b051c8f6c 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -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>> { let _p = profile::span("handle_code_lens"); - let mut lenses: Vec = 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, ¶ms.text_document.uri)?; - let line_index = snap.analysis.file_line_index(file_id)?; - let cargo_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); - } + let cargo_target_spec = CargoTargetSpec::for_file(&snap, file_id)?; - 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()), - }) - } - - if lens_config.refs { - lenses.push(CodeLens { - range, - command: None, - data: Some(to_value(CodeLensResolveData::References(doc_pos)).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.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 { - let _p = profile::span("handle_code_lens_resolve"); - let data = code_lens.data.unwrap(); - let resolve = from_json::>("CodeLensResolveData", data)?; - match resolve { - Some(CodeLensResolveData::Impls(lens_params)) => { - let locations: Vec = - 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()); - 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, - ) - }; + let annotation = from_proto::annotation(&snap, code_lens)?; - 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, -) -> 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())], diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index ce5a0e822..a1ad855c3 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -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), +} diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index be10ac1ae..29fac96fb 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -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 { + 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 = 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 = + 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::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 } diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index 2a966a96d..51aa79517 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@