Skip to content

Commit

Permalink
feat(els): impl language services
Browse files Browse the repository at this point in the history
* document highlight
* selection range
* goto type definition
  • Loading branch information
mtshiba committed Oct 10, 2024
1 parent b7647c7 commit e7d01b8
Show file tree
Hide file tree
Showing 10 changed files with 401 additions and 14 deletions.
3 changes: 3 additions & 0 deletions crates/els/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ ELS is a language server for the [Erg](https://github.com/erg-lang/erg) programm
- [x] Diagnostics
- [x] Hover
- [x] Go to definition
- [x] Go to type definition
- [x] Go to implementation
- [x] Find references
- [x] Renaming
Expand All @@ -28,9 +29,11 @@ ELS is a language server for the [Erg](https://github.com/erg-lang/erg) programm
- [x] Signature help
- [x] Workspace symbol
- [x] Document symbol
- [x] Document highlight
- [x] Call hierarchy
- [x] Folding range
- [x] Folding imports
- [x] Selection range

## Installation

Expand Down
46 changes: 39 additions & 7 deletions crates/els/channels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@ use erg_compiler::erg_parser::parse::Parsable;
use lsp_types::request::{
CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion,
DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest, GotoDefinition, GotoImplementation,
GotoImplementationParams, HoverRequest, InlayHintRequest, InlayHintResolveRequest, References,
ResolveCompletionItem, SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles,
WorkspaceSymbol,
DocumentHighlightRequest, DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest,
GotoDefinition, GotoImplementation, GotoImplementationParams, GotoTypeDefinition,
GotoTypeDefinitionParams, HoverRequest, InlayHintRequest, InlayHintResolveRequest, References,
ResolveCompletionItem, SelectionRangeRequest, SemanticTokensFullRequest, SignatureHelpRequest,
WillRenameFiles, WorkspaceSymbol,
};
use lsp_types::{
CallHierarchyIncomingCallsParams, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
CodeAction, CodeActionParams, CodeLensParams, CompletionItem, CompletionParams,
DocumentSymbolParams, ExecuteCommandParams, FoldingRangeParams, GotoDefinitionParams,
HoverParams, InlayHint, InlayHintParams, ReferenceParams, RenameFilesParams,
SemanticTokensParams, SignatureHelpParams, WorkspaceSymbolParams,
DocumentHighlightParams, DocumentSymbolParams, ExecuteCommandParams, FoldingRangeParams,
GotoDefinitionParams, HoverParams, InlayHint, InlayHintParams, ReferenceParams,
RenameFilesParams, SelectionRangeParams, SemanticTokensParams, SignatureHelpParams,
WorkspaceSymbolParams,
};

use crate::server::Server;
Expand All @@ -38,6 +40,7 @@ pub struct SendChannels {
completion: mpsc::Sender<WorkerMessage<CompletionParams>>,
resolve_completion: mpsc::Sender<WorkerMessage<CompletionItem>>,
goto_definition: mpsc::Sender<WorkerMessage<GotoDefinitionParams>>,
goto_type_definition: mpsc::Sender<WorkerMessage<GotoTypeDefinitionParams>>,
goto_implementation: mpsc::Sender<WorkerMessage<GotoImplementationParams>>,
semantic_tokens_full: mpsc::Sender<WorkerMessage<SemanticTokensParams>>,
inlay_hint: mpsc::Sender<WorkerMessage<InlayHintParams>>,
Expand All @@ -56,6 +59,8 @@ pub struct SendChannels {
call_hierarchy_incoming: mpsc::Sender<WorkerMessage<CallHierarchyIncomingCallsParams>>,
call_hierarchy_outgoing: mpsc::Sender<WorkerMessage<CallHierarchyOutgoingCallsParams>>,
folding_range: mpsc::Sender<WorkerMessage<FoldingRangeParams>>,
selection_range: mpsc::Sender<WorkerMessage<SelectionRangeParams>>,
document_highlight: mpsc::Sender<WorkerMessage<DocumentHighlightParams>>,
pub(crate) health_check: mpsc::Sender<WorkerMessage<()>>,
}

Expand All @@ -64,6 +69,7 @@ impl SendChannels {
let (tx_completion, rx_completion) = mpsc::channel();
let (tx_resolve_completion, rx_resolve_completion) = mpsc::channel();
let (tx_goto_definition, rx_goto_definition) = mpsc::channel();
let (tx_goto_type_definition, rx_goto_type_definition) = mpsc::channel();
let (tx_goto_implementation, rx_goto_implementation) = mpsc::channel();
let (tx_semantic_tokens_full, rx_semantic_tokens_full) = mpsc::channel();
let (tx_inlay_hint, rx_inlay_hint) = mpsc::channel();
Expand All @@ -82,12 +88,15 @@ impl SendChannels {
let (tx_call_hierarchy_incoming, rx_call_hierarchy_incoming) = mpsc::channel();
let (tx_call_hierarchy_outgoing, rx_call_hierarchy_outgoing) = mpsc::channel();
let (tx_folding_range, rx_folding_range) = mpsc::channel();
let (tx_selection_range, rx_selection_range) = mpsc::channel();
let (tx_document_highlight, rx_document_highlight) = mpsc::channel();
let (tx_health_check, rx_health_check) = mpsc::channel();
(
Self {
completion: tx_completion,
resolve_completion: tx_resolve_completion,
goto_definition: tx_goto_definition,
goto_type_definition: tx_goto_type_definition,
goto_implementation: tx_goto_implementation,
semantic_tokens_full: tx_semantic_tokens_full,
inlay_hint: tx_inlay_hint,
Expand All @@ -106,12 +115,15 @@ impl SendChannels {
call_hierarchy_incoming: tx_call_hierarchy_incoming,
call_hierarchy_outgoing: tx_call_hierarchy_outgoing,
folding_range: tx_folding_range,
selection_range: tx_selection_range,
document_highlight: tx_document_highlight,
health_check: tx_health_check,
},
ReceiveChannels {
completion: rx_completion,
resolve_completion: rx_resolve_completion,
goto_definition: rx_goto_definition,
goto_type_definition: rx_goto_type_definition,
goto_implementation: rx_goto_implementation,
semantic_tokens_full: rx_semantic_tokens_full,
inlay_hint: rx_inlay_hint,
Expand All @@ -130,6 +142,8 @@ impl SendChannels {
call_hierarchy_incoming: rx_call_hierarchy_incoming,
call_hierarchy_outgoing: rx_call_hierarchy_outgoing,
folding_range: rx_folding_range,
selection_range: rx_selection_range,
document_highlight: rx_document_highlight,
health_check: rx_health_check,
},
)
Expand All @@ -139,6 +153,8 @@ impl SendChannels {
let _ = self.completion.send(WorkerMessage::Kill);
let _ = self.resolve_completion.send(WorkerMessage::Kill);
let _ = self.goto_definition.send(WorkerMessage::Kill);
let _ = self.goto_type_definition.send(WorkerMessage::Kill);
let _ = self.goto_implementation.send(WorkerMessage::Kill);
let _ = self.semantic_tokens_full.send(WorkerMessage::Kill);
let _ = self.inlay_hint.send(WorkerMessage::Kill);
let _ = self.inlay_hint_resolve.send(WorkerMessage::Kill);
Expand All @@ -156,6 +172,8 @@ impl SendChannels {
let _ = self.call_hierarchy_incoming.send(WorkerMessage::Kill);
let _ = self.call_hierarchy_outgoing.send(WorkerMessage::Kill);
let _ = self.folding_range.send(WorkerMessage::Kill);
let _ = self.selection_range.send(WorkerMessage::Kill);
let _ = self.document_highlight.send(WorkerMessage::Kill);
let _ = self.health_check.send(WorkerMessage::Kill);
}
}
Expand All @@ -165,6 +183,7 @@ pub struct ReceiveChannels {
pub(crate) completion: mpsc::Receiver<WorkerMessage<CompletionParams>>,
pub(crate) resolve_completion: mpsc::Receiver<WorkerMessage<CompletionItem>>,
pub(crate) goto_definition: mpsc::Receiver<WorkerMessage<GotoDefinitionParams>>,
pub(crate) goto_type_definition: mpsc::Receiver<WorkerMessage<GotoTypeDefinitionParams>>,
pub(crate) goto_implementation: mpsc::Receiver<WorkerMessage<GotoImplementationParams>>,
pub(crate) semantic_tokens_full: mpsc::Receiver<WorkerMessage<SemanticTokensParams>>,
pub(crate) inlay_hint: mpsc::Receiver<WorkerMessage<InlayHintParams>>,
Expand All @@ -185,6 +204,8 @@ pub struct ReceiveChannels {
pub(crate) call_hierarchy_outgoing:
mpsc::Receiver<WorkerMessage<CallHierarchyOutgoingCallsParams>>,
pub(crate) folding_range: mpsc::Receiver<WorkerMessage<FoldingRangeParams>>,
pub(crate) selection_range: mpsc::Receiver<WorkerMessage<SelectionRangeParams>>,
pub(crate) document_highlight: mpsc::Receiver<WorkerMessage<DocumentHighlightParams>>,
pub(crate) health_check: mpsc::Receiver<WorkerMessage<()>>,
}

Expand Down Expand Up @@ -222,6 +243,11 @@ macro_rules! impl_sendable {
impl_sendable!(Completion, CompletionParams, completion);
impl_sendable!(ResolveCompletionItem, CompletionItem, resolve_completion);
impl_sendable!(GotoDefinition, GotoDefinitionParams, goto_definition);
impl_sendable!(
GotoTypeDefinition,
GotoTypeDefinitionParams,
goto_type_definition
);
impl_sendable!(
GotoImplementation,
GotoImplementationParams,
Expand Down Expand Up @@ -260,3 +286,9 @@ impl_sendable!(
call_hierarchy_outgoing
);
impl_sendable!(FoldingRangeRequest, FoldingRangeParams, folding_range);
impl_sendable!(SelectionRangeRequest, SelectionRangeParams, selection_range);
impl_sendable!(
DocumentHighlightRequest,
DocumentHighlightParams,
document_highlight
);
47 changes: 47 additions & 0 deletions crates/els/doc_highlight.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use erg_compiler::artifact::BuildRunnable;
use erg_compiler::erg_parser::parse::Parsable;

use lsp_types::{DocumentHighlight, DocumentHighlightKind, DocumentHighlightParams, Position};

use crate::_log;
use crate::server::{ELSResult, RedirectableStdout, Server};
use crate::util::{loc_to_range, NormalizedUrl};

impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
pub(crate) fn handle_document_highlight(
&mut self,
params: DocumentHighlightParams,
) -> ELSResult<Option<Vec<DocumentHighlight>>> {
_log!(self, "document highlight requested: {params:?}");
let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri);
let mut res = vec![];
res.extend(
self.get_document_highlight(&uri, params.text_document_position_params.position),
);
Ok(Some(res))
}

fn get_document_highlight(&self, uri: &NormalizedUrl, pos: Position) -> Vec<DocumentHighlight> {
let mut res = vec![];
let Some(visitor) = self.get_visitor(uri) else {
return res;
};
if let Some(tok) = self.file_cache.get_symbol(uri, pos) {
if let Some(vi) = visitor.get_info(&tok) {
if let Some(range) = loc_to_range(vi.def_loc.loc) {
res.push(DocumentHighlight {
range,
kind: Some(DocumentHighlightKind::TEXT),
});
}
for reference in self.get_refs_from_abs_loc(&vi.def_loc) {
res.push(DocumentHighlight {
range: reference.range,
kind: Some(DocumentHighlightKind::TEXT),
});
}
}
}
res
}
}
170 changes: 170 additions & 0 deletions crates/els/hir_visitor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use erg_common::consts::ERG_MODE;
use erg_common::error::Location;
use erg_common::shared::MappedRwLockReadGuard;
use erg_common::traits::Locational;
use erg_common::Str;
Expand Down Expand Up @@ -820,4 +821,173 @@ impl<'a> HIRVisitor<'a> {
self.get_expr_info(&tasc.expr, token)
.or_else(|| self.get_expr_info(&tasc.spec.expr, token))
}

pub fn get_parent(&self, expr_loc: Location) -> Option<&Expr> {
for chunk in self.hir.module.iter() {
if let Some(parent) = self.get_parent_expr(chunk, expr_loc) {
return Some(parent);
}
}
None
}

#[allow(clippy::only_used_in_recursion)]
fn get_parent_expr<'p>(&self, maybe_parent: &'p Expr, expr_loc: Location) -> Option<&'p Expr> {
let loc = maybe_parent.loc();
#[allow(clippy::double_comparisons)]
if loc < expr_loc || loc > expr_loc {
return None;
}
match maybe_parent {
Expr::BinOp(bin) => (bin.lhs.loc() == expr_loc || bin.rhs.loc() == expr_loc)
.then_some(maybe_parent)
.or_else(|| {
self.get_parent_expr(&bin.lhs, expr_loc)
.or_else(|| self.get_parent_expr(&bin.rhs, expr_loc))
}),
Expr::UnaryOp(unary) => (unary.expr.loc() == expr_loc)
.then_some(maybe_parent)
.or_else(|| self.get_parent_expr(&unary.expr, expr_loc)),
Expr::Accessor(Accessor::Attr(attr)) => (attr.obj.loc() == expr_loc
|| attr.ident.loc() == expr_loc)
.then_some(maybe_parent)
.or_else(|| self.get_parent_expr(&attr.obj, expr_loc)),
Expr::Call(call) => {
if call.obj.loc() == expr_loc || call.args.iter().any(|arg| arg.loc() == expr_loc) {
return Some(maybe_parent);
}
if let Some(expr) = self.get_parent_expr(&call.obj, expr_loc) {
return Some(expr);
}
for arg in call.args.pos_args.iter() {
if let Some(parent) = self.get_parent_expr(&arg.expr, expr_loc) {
return Some(parent);
}
}
if let Some(var) = &call.args.var_args {
if let Some(parent) = self.get_parent_expr(&var.expr, expr_loc) {
return Some(parent);
}
}
for arg in call.args.kw_args.iter() {
if let Some(parent) = self.get_parent_expr(&arg.expr, expr_loc) {
return Some(parent);
}
}
if let Some(kw_var) = &call.args.kw_var {
if let Some(parent) = self.get_parent_expr(&kw_var.expr, expr_loc) {
return Some(parent);
}
}
None
}
Expr::Def(def) => {
if def.body.block.iter().any(|chunk| chunk.loc() == expr_loc) {
return Some(maybe_parent);
}
for chunk in def.body.block.iter() {
if let Some(parent) = self.get_parent_expr(chunk, expr_loc) {
return Some(parent);
}
}
None
}
Expr::Lambda(lambda) => {
if lambda.body.iter().any(|chunk| chunk.loc() == expr_loc) {
return Some(maybe_parent);
}
for chunk in lambda.body.iter() {
if let Some(parent) = self.get_parent_expr(chunk, expr_loc) {
return Some(parent);
}
}
None
}
Expr::TypeAsc(type_asc) => {
if type_asc.expr.loc() == expr_loc {
return Some(maybe_parent);
}
if let Some(parent) = self.get_parent_expr(&type_asc.expr, expr_loc) {
return Some(parent);
}
None
}
Expr::ClassDef(class_def) => {
if class_def.all_methods().any(|chunk| chunk.loc() == expr_loc) {
return Some(maybe_parent);
}
for chunk in class_def.all_methods() {
if let Some(parent) = self.get_parent_expr(chunk, expr_loc) {
return Some(parent);
}
}
None
}
Expr::List(List::Normal(list)) => {
if list.elems.iter().any(|elem| elem.loc() == expr_loc) {
return Some(maybe_parent);
}
for elem in list.elems.pos_args.iter() {
if let Some(parent) = self.get_parent_expr(&elem.expr, expr_loc) {
return Some(parent);
}
}
None
}
Expr::Set(Set::Normal(set)) => {
if set.elems.iter().any(|elem| elem.loc() == expr_loc) {
return Some(maybe_parent);
}
for elem in set.elems.pos_args.iter() {
if let Some(parent) = self.get_parent_expr(&elem.expr, expr_loc) {
return Some(parent);
}
}
None
}
Expr::Tuple(Tuple::Normal(tuple)) => {
if tuple.elems.iter().any(|elem| elem.loc() == expr_loc) {
return Some(maybe_parent);
}
for elem in tuple.elems.pos_args.iter() {
if let Some(parent) = self.get_parent_expr(&elem.expr, expr_loc) {
return Some(parent);
}
}
None
}
Expr::Dict(Dict::Normal(dict)) => {
if dict
.kvs
.iter()
.any(|kv| kv.key.loc() == expr_loc || kv.value.loc() == expr_loc)
{
return Some(maybe_parent);
}
for kv in dict.kvs.iter() {
if let Some(parent) = self.get_parent_expr(&kv.key, expr_loc) {
return Some(parent);
}
if let Some(parent) = self.get_parent_expr(&kv.value, expr_loc) {
return Some(parent);
}
}
None
}
Expr::Record(record) => {
if record.attrs.iter().any(|field| field.loc() == expr_loc) {
return Some(maybe_parent);
}
for field in record.attrs.iter() {
for chunk in field.body.block.iter() {
if let Some(parent) = self.get_parent_expr(chunk, expr_loc) {
return Some(parent);
}
}
}
None
}
_ => None,
}
}
}
Loading

0 comments on commit e7d01b8

Please sign in to comment.