From d4699cf997afab1e7a5e78f8a9e06cf89901aedf Mon Sep 17 00:00:00 2001 From: Shunsuke Shibayama Date: Sat, 2 Sep 2023 01:21:50 +0900 Subject: [PATCH] feat(els): add call hierarchy --- crates/els/README.md | 1 + crates/els/call_hierarchy.rs | 99 ++++++++++++++++++++++++++++++++++++ crates/els/channels.rs | 43 ++++++++++++++++ crates/els/hir_visitor.rs | 21 ++++++-- crates/els/lib.rs | 1 + crates/els/main.rs | 1 + crates/els/server.rs | 35 ++++++++++--- crates/els/sig_help.rs | 15 ++++-- crates/els/symbol.rs | 2 +- 9 files changed, 200 insertions(+), 18 deletions(-) create mode 100644 crates/els/call_hierarchy.rs diff --git a/crates/els/README.md b/crates/els/README.md index d1f3dd0fb..67559c930 100644 --- a/crates/els/README.md +++ b/crates/els/README.md @@ -27,6 +27,7 @@ ELS is a language server for the [Erg](https://github.com/erg-lang/erg) programm - [x] show trait implementations - [x] Signature help - [x] Workspace symbol +- [x] Call hierarchy ## Installation diff --git a/crates/els/call_hierarchy.rs b/crates/els/call_hierarchy.rs new file mode 100644 index 000000000..1b7dd2fe6 --- /dev/null +++ b/crates/els/call_hierarchy.rs @@ -0,0 +1,99 @@ +use std::str::FromStr; + +use erg_compiler::artifact::BuildRunnable; +use erg_compiler::erg_parser::parse::Parsable; + +use erg_compiler::hir::Def; +use erg_compiler::varinfo::{AbsLocation, VarInfo}; +use lsp_types::{ + CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, + CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, +}; + +use crate::_log; +use crate::server::{ELSResult, Server}; +use crate::symbol::symbol_kind; +use crate::util::{abs_loc_to_lsp_loc, loc_to_pos, NormalizedUrl}; + +fn hierarchy_item(name: String, vi: &VarInfo) -> Option { + let loc = abs_loc_to_lsp_loc(&vi.def_loc)?; + Some(CallHierarchyItem { + name, + kind: symbol_kind(vi), + tags: None, + detail: Some(vi.t.to_string()), + uri: loc.uri, + range: loc.range, + selection_range: loc.range, + data: Some(vi.def_loc.to_string().into()), + }) +} + +impl Server { + pub(crate) fn handle_call_hierarchy_incoming( + &mut self, + params: CallHierarchyIncomingCallsParams, + ) -> ELSResult>> { + let mut res = vec![]; + _log!("call hierarchy incoming calls requested: {params:?}"); + let data = params.item.data.as_ref().unwrap().as_str().unwrap(); + let loc = AbsLocation::from_str(data).unwrap(); + let shared = self.get_shared().unwrap(); + if let Some(refs) = shared.index.get_refs(&loc) { + for referrer_loc in refs.referrers.iter() { + let Some(uri) = referrer_loc + .module + .as_ref() + .and_then(|path| NormalizedUrl::from_file_path(path).ok()) + else { + continue; + }; + let Some(pos) = loc_to_pos(referrer_loc.loc) else { + continue; + }; + if let Some(def) = self.get_min::(&uri, pos) { + if def.sig.is_subr() { + let Some(from) = + hierarchy_item(def.sig.inspect().to_string(), &def.sig.ident().vi) + else { + continue; + }; + let call = CallHierarchyIncomingCall { + from, + from_ranges: vec![], + }; + res.push(call); + } + } + } + } + Ok(Some(res)) + } + + pub(crate) fn handle_call_hierarchy_outgoing( + &mut self, + params: CallHierarchyOutgoingCallsParams, + ) -> ELSResult>> { + _log!("call hierarchy outgoing calls requested: {params:?}"); + Ok(None) + } + + pub(crate) fn handle_call_hierarchy_prepare( + &mut self, + params: CallHierarchyPrepareParams, + ) -> ELSResult>> { + _log!("call hierarchy prepare requested: {params:?}"); + let mut res = vec![]; + let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri); + let pos = params.text_document_position_params.position; + if let Some(token) = self.file_cache.get_symbol(&uri, pos) { + if let Some(vi) = self.get_definition(&uri, &token)? { + let Some(item) = hierarchy_item(token.content.to_string(), &vi) else { + return Ok(None); + }; + res.push(item); + } + } + Ok(Some(res)) + } +} diff --git a/crates/els/channels.rs b/crates/els/channels.rs index ea862786c..1284e9208 100644 --- a/crates/els/channels.rs +++ b/crates/els/channels.rs @@ -4,12 +4,14 @@ use erg_compiler::artifact::BuildRunnable; use erg_compiler::erg_parser::parse::Parsable; use lsp_types::request::{ + CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare, CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion, ExecuteCommand, GotoDefinition, HoverRequest, InlayHintRequest, InlayHintResolveRequest, References, ResolveCompletionItem, SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, WorkspaceSymbol, }; use lsp_types::{ + CallHierarchyIncomingCallsParams, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, CodeAction, CodeActionParams, CodeLensParams, CompletionItem, CompletionParams, ExecuteCommandParams, GotoDefinitionParams, HoverParams, InlayHint, InlayHintParams, ReferenceParams, RenameFilesParams, SemanticTokensParams, SignatureHelpParams, @@ -47,6 +49,9 @@ pub struct SendChannels { will_rename_files: mpsc::Sender>, execute_command: mpsc::Sender>, workspace_symbol: mpsc::Sender>, + call_hierarchy_prepare: mpsc::Sender>, + call_hierarchy_incoming: mpsc::Sender>, + call_hierarchy_outgoing: mpsc::Sender>, pub(crate) health_check: mpsc::Sender>, } @@ -67,6 +72,9 @@ impl SendChannels { let (tx_will_rename_files, rx_will_rename_files) = mpsc::channel(); let (tx_execute_command, rx_execute_command) = mpsc::channel(); let (tx_workspace_symbol, rx_workspace_symbol) = mpsc::channel(); + let (tx_call_hierarchy_prepare, rx_call_hierarchy_prepare) = mpsc::channel(); + let (tx_call_hierarchy_incoming, rx_call_hierarchy_incoming) = mpsc::channel(); + let (tx_call_hierarchy_outgoing, rx_call_hierarchy_outgoing) = mpsc::channel(); let (tx_health_check, rx_health_check) = mpsc::channel(); ( Self { @@ -85,6 +93,9 @@ impl SendChannels { will_rename_files: tx_will_rename_files, execute_command: tx_execute_command, workspace_symbol: tx_workspace_symbol, + call_hierarchy_prepare: tx_call_hierarchy_prepare, + call_hierarchy_incoming: tx_call_hierarchy_incoming, + call_hierarchy_outgoing: tx_call_hierarchy_outgoing, health_check: tx_health_check, }, ReceiveChannels { @@ -103,6 +114,9 @@ impl SendChannels { will_rename_files: rx_will_rename_files, execute_command: rx_execute_command, workspace_symbol: rx_workspace_symbol, + call_hierarchy_prepare: rx_call_hierarchy_prepare, + call_hierarchy_incoming: rx_call_hierarchy_incoming, + call_hierarchy_outgoing: rx_call_hierarchy_outgoing, health_check: rx_health_check, }, ) @@ -124,6 +138,15 @@ impl SendChannels { self.will_rename_files.send(WorkerMessage::Kill).unwrap(); self.execute_command.send(WorkerMessage::Kill).unwrap(); self.workspace_symbol.send(WorkerMessage::Kill).unwrap(); + self.call_hierarchy_prepare + .send(WorkerMessage::Kill) + .unwrap(); + self.call_hierarchy_incoming + .send(WorkerMessage::Kill) + .unwrap(); + self.call_hierarchy_outgoing + .send(WorkerMessage::Kill) + .unwrap(); self.health_check.send(WorkerMessage::Kill).unwrap(); } } @@ -145,6 +168,11 @@ pub struct ReceiveChannels { pub(crate) will_rename_files: mpsc::Receiver>, pub(crate) execute_command: mpsc::Receiver>, pub(crate) workspace_symbol: mpsc::Receiver>, + pub(crate) call_hierarchy_prepare: mpsc::Receiver>, + pub(crate) call_hierarchy_incoming: + mpsc::Receiver>, + pub(crate) call_hierarchy_outgoing: + mpsc::Receiver>, pub(crate) health_check: mpsc::Receiver>, } @@ -188,3 +216,18 @@ impl_sendable!(SignatureHelpRequest, SignatureHelpParams, signature_help); impl_sendable!(WillRenameFiles, RenameFilesParams, will_rename_files); impl_sendable!(ExecuteCommand, ExecuteCommandParams, execute_command); impl_sendable!(WorkspaceSymbol, WorkspaceSymbolParams, workspace_symbol); +impl_sendable!( + CallHierarchyPrepare, + CallHierarchyPrepareParams, + call_hierarchy_prepare +); +impl_sendable!( + CallHierarchyIncomingCalls, + CallHierarchyIncomingCallsParams, + call_hierarchy_incoming +); +impl_sendable!( + CallHierarchyOutgoingCalls, + CallHierarchyOutgoingCallsParams, + call_hierarchy_outgoing +); diff --git a/crates/els/hir_visitor.rs b/crates/els/hir_visitor.rs index 31493b560..cb9abd13d 100644 --- a/crates/els/hir_visitor.rs +++ b/crates/els/hir_visitor.rs @@ -28,12 +28,23 @@ impl PosLocational for Position { } } +pub trait GetExprKind { + const KIND: ExprKind; +} +impl GetExprKind for Expr { + const KIND: ExprKind = ExprKind::Expr; +} +impl GetExprKind for Call { + const KIND: ExprKind = ExprKind::Call; +} +impl GetExprKind for Def { + const KIND: ExprKind = ExprKind::Def; +} + #[derive(Debug)] pub enum ExprKind { Call, - #[allow(dead_code)] - SubrDef, - // Def, + Def, Expr, } @@ -41,8 +52,8 @@ impl ExprKind { pub const fn matches(&self, expr: &Expr) -> bool { match (self, expr) { (ExprKind::Call, Expr::Call(_)) | (ExprKind::Expr, _) => true, - // (ExprKind::Def, Expr::Def(_)) => true, - (ExprKind::SubrDef, Expr::Def(def)) => def.sig.is_subr(), + (ExprKind::Def, Expr::Def(_)) => true, + // (ExprKind::Def, Expr::Def(def)) => def.sig.is_subr(), _ => false, } } diff --git a/crates/els/lib.rs b/crates/els/lib.rs index 9576087dc..bf93b6840 100644 --- a/crates/els/lib.rs +++ b/crates/els/lib.rs @@ -1,3 +1,4 @@ +mod call_hierarchy; mod channels; mod code_action; mod code_lens; diff --git a/crates/els/main.rs b/crates/els/main.rs index 181bc86f6..319b2c9ad 100644 --- a/crates/els/main.rs +++ b/crates/els/main.rs @@ -1,3 +1,4 @@ +mod call_hierarchy; mod channels; mod code_action; mod code_lens; diff --git a/crates/els/server.rs b/crates/els/server.rs index 46abe18dd..7e0fc0b28 100644 --- a/crates/els/server.rs +++ b/crates/els/server.rs @@ -27,19 +27,20 @@ use erg_compiler::module::{SharedCompilerResource, SharedModuleGraph, SharedModu use erg_compiler::ty::HasType; use lsp_types::request::{ + CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare, CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion, ExecuteCommand, GotoDefinition, HoverRequest, InlayHintRequest, InlayHintResolveRequest, References, Rename, Request, ResolveCompletionItem, SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, WorkspaceSymbol, }; use lsp_types::{ - CodeActionKind, CodeActionOptions, CodeActionProviderCapability, CodeLensOptions, - CompletionOptions, ConfigurationItem, ConfigurationParams, DidChangeTextDocumentParams, - DidOpenTextDocumentParams, ExecuteCommandOptions, HoverProviderCapability, InitializeParams, - InitializeResult, InlayHintOptions, InlayHintServerCapabilities, OneOf, Position, - SemanticTokenType, SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, - SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelpOptions, - WorkDoneProgressOptions, + CallHierarchyServerCapability, CodeActionKind, CodeActionOptions, CodeActionProviderCapability, + CodeLensOptions, CompletionOptions, ConfigurationItem, ConfigurationParams, + DidChangeTextDocumentParams, DidOpenTextDocumentParams, ExecuteCommandOptions, + HoverProviderCapability, InitializeParams, InitializeResult, InlayHintOptions, + InlayHintServerCapabilities, OneOf, Position, SemanticTokenType, SemanticTokensFullOptions, + SemanticTokensLegend, SemanticTokensOptions, SemanticTokensServerCapabilities, + ServerCapabilities, SignatureHelpOptions, WorkDoneProgressOptions, }; use serde::{Deserialize, Serialize}; @@ -503,6 +504,7 @@ impl Server { resolve_provider: Some(false), }); capabilities.workspace_symbol_provider = Some(OneOf::Left(true)); + capabilities.call_hierarchy_provider = Some(CallHierarchyServerCapability::Simple(true)); capabilities } @@ -566,6 +568,18 @@ impl Server { receivers.workspace_symbol, Self::handle_workspace_symbol, ); + self.start_service::( + receivers.call_hierarchy_prepare, + Self::handle_call_hierarchy_prepare, + ); + self.start_service::( + receivers.call_hierarchy_incoming, + Self::handle_call_hierarchy_incoming, + ); + self.start_service::( + receivers.call_hierarchy_outgoing, + Self::handle_call_hierarchy_outgoing, + ); self.start_client_health_checker(receivers.health_check); } @@ -746,6 +760,13 @@ impl Server { WillRenameFiles::METHOD => self.parse_send::(id, msg), ExecuteCommand::METHOD => self.parse_send::(id, msg), WorkspaceSymbol::METHOD => self.parse_send::(id, msg), + CallHierarchyIncomingCalls::METHOD => { + self.parse_send::(id, msg) + } + CallHierarchyOutgoingCalls::METHOD => { + self.parse_send::(id, msg) + } + CallHierarchyPrepare::METHOD => self.parse_send::(id, msg), other => send_error(Some(id), -32600, format!("{other} is not supported")), } } diff --git a/crates/els/sig_help.rs b/crates/els/sig_help.rs index b9ac68e4c..720b0b8f1 100644 --- a/crates/els/sig_help.rs +++ b/crates/els/sig_help.rs @@ -10,7 +10,7 @@ use lsp_types::{ SignatureHelpParams, SignatureHelpTriggerKind, SignatureInformation, }; -use crate::hir_visitor::ExprKind; +use crate::hir_visitor::GetExprKind; use crate::server::{send_log, ELSResult, Server}; use crate::util::{loc_to_pos, pos_to_loc, NormalizedUrl}; @@ -92,9 +92,14 @@ impl Server { None } - pub(crate) fn get_min_call(&self, uri: &NormalizedUrl, pos: Position) -> Option { - self.get_searcher(uri, ExprKind::Call) + pub(crate) fn get_min + GetExprKind>( + &self, + uri: &NormalizedUrl, + pos: Position, + ) -> Option { + self.get_searcher(uri, E::KIND) .and_then(|visitor| visitor.get_min_expr(pos).cloned()) + .and_then(|expr| E::try_from(expr).ok()) } pub(crate) fn nth(&self, uri: &NormalizedUrl, call: &Call, pos: Position) -> usize { @@ -136,7 +141,7 @@ impl Server { ) -> Option { if let Some(token) = self.file_cache.get_token(uri, pos) { crate::_log!("token: {token}"); - if let Some(Expr::Call(call)) = self.get_min_call(uri, pos) { + if let Some(call) = self.get_min::(uri, pos) { if call.ln_begin() > token.ln_begin() || call.ln_end() < token.ln_end() { return None; } @@ -161,7 +166,7 @@ impl Server { } fn get_continuous_help(&mut self, uri: &NormalizedUrl, pos: Position) -> Option { - if let Some(Expr::Call(call)) = self.get_min_call(uri, pos) { + if let Some(call) = self.get_min::(uri, pos) { let nth = self.nth(uri, &call, pos) as u32 + 1; let help = self.make_sig_help(call.obj.as_ref(), nth); return help; diff --git a/crates/els/symbol.rs b/crates/els/symbol.rs index c658b9cd7..05844ff21 100644 --- a/crates/els/symbol.rs +++ b/crates/els/symbol.rs @@ -9,7 +9,7 @@ use crate::_log; use crate::server::{ELSResult, Server}; use crate::util::abs_loc_to_lsp_loc; -fn symbol_kind(vi: &VarInfo) -> SymbolKind { +pub(crate) fn symbol_kind(vi: &VarInfo) -> SymbolKind { match &vi.t { Type::Subr(subr) if subr.self_t().is_some() => SymbolKind::METHOD, Type::Quantified(quant) if quant.self_t().is_some() => SymbolKind::METHOD,