mirror of
https://github.com/erg-lang/erg.git
synced 2025-09-28 20:14:45 +00:00
feat(els): add goto implementation
This commit is contained in:
parent
d8835fd169
commit
3f678ef5e3
8 changed files with 106 additions and 58 deletions
|
@ -13,7 +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
|
||||
- [ ] Go to implementation
|
||||
- [x] Go to implementation
|
||||
- [x] Find references
|
||||
- [x] Renaming
|
||||
- [x] Inlay hint
|
||||
|
|
|
@ -6,9 +6,10 @@ use erg_compiler::erg_parser::parse::Parsable;
|
|||
use lsp_types::request::{
|
||||
CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
|
||||
CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion,
|
||||
DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest, GotoDefinition, HoverRequest,
|
||||
InlayHintRequest, InlayHintResolveRequest, References, ResolveCompletionItem,
|
||||
SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, WorkspaceSymbol,
|
||||
DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest, GotoDefinition, GotoImplementation,
|
||||
GotoImplementationParams, HoverRequest, InlayHintRequest, InlayHintResolveRequest, References,
|
||||
ResolveCompletionItem, SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles,
|
||||
WorkspaceSymbol,
|
||||
};
|
||||
use lsp_types::{
|
||||
CallHierarchyIncomingCallsParams, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
|
||||
|
@ -37,6 +38,7 @@ pub struct SendChannels {
|
|||
completion: mpsc::Sender<WorkerMessage<CompletionParams>>,
|
||||
resolve_completion: mpsc::Sender<WorkerMessage<CompletionItem>>,
|
||||
goto_definition: mpsc::Sender<WorkerMessage<GotoDefinitionParams>>,
|
||||
goto_implementation: mpsc::Sender<WorkerMessage<GotoImplementationParams>>,
|
||||
semantic_tokens_full: mpsc::Sender<WorkerMessage<SemanticTokensParams>>,
|
||||
inlay_hint: mpsc::Sender<WorkerMessage<InlayHintParams>>,
|
||||
inlay_hint_resolve: mpsc::Sender<WorkerMessage<InlayHint>>,
|
||||
|
@ -62,6 +64,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_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();
|
||||
let (tx_inlay_hint_resolve, rx_inlay_hint_resolve) = mpsc::channel();
|
||||
|
@ -85,6 +88,7 @@ impl SendChannels {
|
|||
completion: tx_completion,
|
||||
resolve_completion: tx_resolve_completion,
|
||||
goto_definition: tx_goto_definition,
|
||||
goto_implementation: tx_goto_implementation,
|
||||
semantic_tokens_full: tx_semantic_tokens_full,
|
||||
inlay_hint: tx_inlay_hint,
|
||||
inlay_hint_resolve: tx_inlay_hint_resolve,
|
||||
|
@ -108,6 +112,7 @@ impl SendChannels {
|
|||
completion: rx_completion,
|
||||
resolve_completion: rx_resolve_completion,
|
||||
goto_definition: rx_goto_definition,
|
||||
goto_implementation: rx_goto_implementation,
|
||||
semantic_tokens_full: rx_semantic_tokens_full,
|
||||
inlay_hint: rx_inlay_hint,
|
||||
inlay_hint_resolve: rx_inlay_hint_resolve,
|
||||
|
@ -166,6 +171,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_implementation: mpsc::Receiver<WorkerMessage<GotoImplementationParams>>,
|
||||
pub(crate) semantic_tokens_full: mpsc::Receiver<WorkerMessage<SemanticTokensParams>>,
|
||||
pub(crate) inlay_hint: mpsc::Receiver<WorkerMessage<InlayHintParams>>,
|
||||
pub(crate) inlay_hint_resolve: mpsc::Receiver<WorkerMessage<InlayHint>>,
|
||||
|
@ -212,6 +218,11 @@ macro_rules! impl_sendable {
|
|||
impl_sendable!(Completion, CompletionParams, completion);
|
||||
impl_sendable!(ResolveCompletionItem, CompletionItem, resolve_completion);
|
||||
impl_sendable!(GotoDefinition, GotoDefinitionParams, goto_definition);
|
||||
impl_sendable!(
|
||||
GotoImplementation,
|
||||
GotoImplementationParams,
|
||||
goto_implementation
|
||||
);
|
||||
impl_sendable!(
|
||||
SemanticTokensFullRequest,
|
||||
SemanticTokensParams,
|
||||
|
|
|
@ -4,11 +4,11 @@ use erg_compiler::artifact::BuildRunnable;
|
|||
use erg_compiler::context::register::PylyzerStatus;
|
||||
use erg_compiler::erg_parser::parse::Parsable;
|
||||
use erg_compiler::erg_parser::token::{Token, TokenCategory};
|
||||
use erg_compiler::hir::Expr;
|
||||
use erg_compiler::hir::{Def, Expr};
|
||||
use erg_compiler::ty::HasType;
|
||||
use erg_compiler::varinfo::VarInfo;
|
||||
|
||||
use lsp_types::{GotoDefinitionParams, GotoDefinitionResponse, Url};
|
||||
use lsp_types::{GotoDefinitionParams, GotoDefinitionResponse, Location, Position, Url};
|
||||
|
||||
use crate::server::{send_log, ELSResult, Server};
|
||||
use crate::util::{self, NormalizedUrl};
|
||||
|
@ -30,58 +30,51 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_definition_response(
|
||||
pub(crate) fn get_definition_location(
|
||||
&self,
|
||||
params: GotoDefinitionParams,
|
||||
) -> ELSResult<GotoDefinitionResponse> {
|
||||
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)? {
|
||||
uri: &NormalizedUrl,
|
||||
pos: Position,
|
||||
) -> ELSResult<Option<Location>> {
|
||||
if let Some(token) = self.file_cache.get_symbol(uri, pos) {
|
||||
if let Some(vi) = self.get_definition(uri, &token)? {
|
||||
// If the target variable is an imported one, jump to the definition file.
|
||||
// Else if the target variable is an alias, jump to the definition of it.
|
||||
// `foo = import "foo"` => jump to `foo.er`
|
||||
// `{x;} = import "foo"` => jump to `x` of `foo.er`
|
||||
if vi.def_loc.module == Some(util::uri_to_path(&uri))
|
||||
if vi.def_loc.module == Some(util::uri_to_path(uri))
|
||||
&& vi.def_loc.loc == token.loc()
|
||||
{
|
||||
if let Some((_, Expr::Def(def))) = self.get_min_expr(&uri, pos, 0) {
|
||||
if let Some(def) = self.get_min::<Def>(uri, pos) {
|
||||
if def.def_kind().is_import() {
|
||||
if vi.t.is_module() {
|
||||
if let Some(path) = self
|
||||
.get_local_ctx(&uri, pos)
|
||||
.get_local_ctx(uri, pos)
|
||||
.first()
|
||||
.and_then(|ctx| ctx.get_path_with_mod_t(&vi.t))
|
||||
{
|
||||
let mod_uri = Url::from_file_path(path).unwrap();
|
||||
let resp = GotoDefinitionResponse::Array(vec![
|
||||
lsp_types::Location::new(
|
||||
mod_uri,
|
||||
lsp_types::Range::default(),
|
||||
),
|
||||
]);
|
||||
return Ok(resp);
|
||||
return Ok(Some(lsp_types::Location::new(
|
||||
mod_uri,
|
||||
lsp_types::Range::default(),
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
// line of module member definitions may no longer match after the desugaring process
|
||||
let mod_t = def.body.ref_t();
|
||||
if let Some((_, vi)) = self
|
||||
.get_local_ctx(&uri, pos)
|
||||
.get_local_ctx(uri, pos)
|
||||
.first()
|
||||
.and_then(|ctx| ctx.get_mod_with_t(mod_t))
|
||||
.and_then(|mod_ctx| mod_ctx.get_var_info(token.inspect()))
|
||||
{
|
||||
let Some(path) = vi.def_loc.module.as_ref() else {
|
||||
return Ok(GotoDefinitionResponse::Array(vec![]));
|
||||
return Ok(None);
|
||||
};
|
||||
let def_uri = Url::from_file_path(path).unwrap();
|
||||
let resp = GotoDefinitionResponse::Array(vec![
|
||||
lsp_types::Location::new(
|
||||
def_uri,
|
||||
util::loc_to_range(vi.def_loc.loc).unwrap(),
|
||||
),
|
||||
]);
|
||||
return Ok(resp);
|
||||
return Ok(Some(lsp_types::Location::new(
|
||||
def_uri,
|
||||
util::loc_to_range(vi.def_loc.loc).unwrap(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
} else if let Expr::Accessor(acc) = def.body.block.last().unwrap() {
|
||||
|
@ -103,14 +96,11 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|||
path.clone()
|
||||
};
|
||||
let def_uri = Url::from_file_path(def_file).unwrap();
|
||||
let resp = GotoDefinitionResponse::Array(vec![
|
||||
lsp_types::Location::new(def_uri, range),
|
||||
]);
|
||||
return Ok(resp);
|
||||
return Ok(Some(lsp_types::Location::new(def_uri, range)));
|
||||
}
|
||||
_ => {
|
||||
send_log("not found (maybe builtin)")?;
|
||||
return Ok(GotoDefinitionResponse::Array(vec![]));
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,21 +109,19 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|||
match (vi.def_loc.module, util::loc_to_range(vi.def_loc.loc)) {
|
||||
(Some(path), Some(range)) => {
|
||||
let def_uri = Url::from_file_path(path).unwrap();
|
||||
Ok(GotoDefinitionResponse::Array(vec![
|
||||
lsp_types::Location::new(def_uri, range),
|
||||
]))
|
||||
Ok(Some(lsp_types::Location::new(def_uri, range)))
|
||||
}
|
||||
_ => {
|
||||
send_log("not found (maybe builtin)")?;
|
||||
Ok(GotoDefinitionResponse::Array(vec![]))
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(GotoDefinitionResponse::Array(vec![]))
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
send_log("lex error occurred")?;
|
||||
Ok(GotoDefinitionResponse::Array(vec![]))
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,7 +130,9 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|||
params: GotoDefinitionParams,
|
||||
) -> ELSResult<Option<GotoDefinitionResponse>> {
|
||||
send_log(format!("definition requested: {params:?}"))?;
|
||||
let result = self.get_definition_response(params)?;
|
||||
Ok(Some(result))
|
||||
let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri);
|
||||
let pos = params.text_document_position_params.position;
|
||||
let result = self.get_definition_location(&uri, pos)?;
|
||||
Ok(result.map(GotoDefinitionResponse::Scalar))
|
||||
}
|
||||
}
|
||||
|
|
39
crates/els/implementation.rs
Normal file
39
crates/els/implementation.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use erg_compiler::artifact::BuildRunnable;
|
||||
use erg_compiler::erg_parser::parse::Parsable;
|
||||
use lsp_types::request::{GotoImplementationParams, GotoImplementationResponse};
|
||||
|
||||
use crate::_log;
|
||||
use crate::server::{ELSResult, Server};
|
||||
use crate::util::{loc_to_pos, NormalizedUrl};
|
||||
|
||||
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
||||
pub(crate) fn handle_goto_implementation(
|
||||
&mut self,
|
||||
params: GotoImplementationParams,
|
||||
) -> ELSResult<Option<GotoImplementationResponse>> {
|
||||
_log!("implementation requested: {params:?}");
|
||||
let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri);
|
||||
let pos = params.text_document_position_params.position;
|
||||
let Some(symbol) = self.file_cache.get_symbol(&uri, pos) else {
|
||||
return Ok(None);
|
||||
};
|
||||
// {x #[ ← this is not the impl ]#;} = import "foo"
|
||||
// print! x # ← symbol
|
||||
if let Some(vi) = self.get_definition(&uri, &symbol)? {
|
||||
let Some(uri) = vi
|
||||
.def_loc
|
||||
.module
|
||||
.and_then(|p| NormalizedUrl::from_file_path(p).ok())
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(pos) = loc_to_pos(vi.def_loc.loc) else {
|
||||
return Ok(None);
|
||||
};
|
||||
if let Some(location) = self.get_definition_location(&uri, pos)? {
|
||||
return Ok(Some(GotoImplementationResponse::Scalar(location)));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ mod file_cache;
|
|||
mod folding_range;
|
||||
mod hir_visitor;
|
||||
mod hover;
|
||||
mod implementation;
|
||||
mod inlay_hint;
|
||||
mod message;
|
||||
mod references;
|
||||
|
|
|
@ -11,6 +11,7 @@ mod file_cache;
|
|||
mod folding_range;
|
||||
mod hir_visitor;
|
||||
mod hover;
|
||||
mod implementation;
|
||||
mod inlay_hint;
|
||||
mod message;
|
||||
mod references;
|
||||
|
|
|
@ -29,19 +29,20 @@ use erg_compiler::ty::HasType;
|
|||
use lsp_types::request::{
|
||||
CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
|
||||
CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion,
|
||||
DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest, GotoDefinition, HoverRequest,
|
||||
InlayHintRequest, InlayHintResolveRequest, References, Rename, Request, ResolveCompletionItem,
|
||||
SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, WorkspaceSymbol,
|
||||
DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest, GotoDefinition, GotoImplementation,
|
||||
HoverRequest, InlayHintRequest, InlayHintResolveRequest, References, Rename, Request,
|
||||
ResolveCompletionItem, SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles,
|
||||
WorkspaceSymbol,
|
||||
};
|
||||
use lsp_types::{
|
||||
CallHierarchyServerCapability, CodeActionKind, CodeActionOptions, CodeActionProviderCapability,
|
||||
CodeLensOptions, CompletionOptions, ConfigurationItem, ConfigurationParams,
|
||||
DidChangeTextDocumentParams, DidOpenTextDocumentParams, ExecuteCommandOptions,
|
||||
FoldingRangeProviderCapability, HoverProviderCapability, InitializeParams, InitializeResult,
|
||||
InlayHintOptions, InlayHintServerCapabilities, OneOf, Position, SemanticTokenType,
|
||||
SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
|
||||
SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelpOptions,
|
||||
WorkDoneProgressOptions,
|
||||
FoldingRangeProviderCapability, HoverProviderCapability, ImplementationProviderCapability,
|
||||
InitializeParams, InitializeResult, InlayHintOptions, InlayHintServerCapabilities, OneOf,
|
||||
Position, SemanticTokenType, SemanticTokensFullOptions, SemanticTokensLegend,
|
||||
SemanticTokensOptions, SemanticTokensServerCapabilities, ServerCapabilities,
|
||||
SignatureHelpOptions, WorkDoneProgressOptions,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -430,6 +431,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|||
capabilities.rename_provider = Some(OneOf::Left(true));
|
||||
capabilities.references_provider = Some(OneOf::Left(true));
|
||||
capabilities.definition_provider = Some(OneOf::Left(true));
|
||||
capabilities.implementation_provider = Some(ImplementationProviderCapability::Simple(true));
|
||||
capabilities.hover_provider = self
|
||||
.disabled_features
|
||||
.contains(&DefaultFeatures::Hover)
|
||||
|
@ -538,6 +540,10 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|||
receivers.goto_definition,
|
||||
Self::handle_goto_definition,
|
||||
);
|
||||
self.start_service::<GotoImplementation>(
|
||||
receivers.goto_implementation,
|
||||
Self::handle_goto_implementation,
|
||||
);
|
||||
self.start_service::<SemanticTokensFullRequest>(
|
||||
receivers.semantic_tokens_full,
|
||||
Self::handle_semantic_tokens_full,
|
||||
|
@ -755,6 +761,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|||
Completion::METHOD => self.parse_send::<Completion>(id, msg),
|
||||
ResolveCompletionItem::METHOD => self.parse_send::<ResolveCompletionItem>(id, msg),
|
||||
GotoDefinition::METHOD => self.parse_send::<GotoDefinition>(id, msg),
|
||||
GotoImplementation::METHOD => self.parse_send::<GotoImplementation>(id, msg),
|
||||
HoverRequest::METHOD => self.parse_send::<HoverRequest>(id, msg),
|
||||
References::METHOD => self.parse_send::<References>(id, msg),
|
||||
SemanticTokensFullRequest::METHOD => {
|
||||
|
|
|
@ -367,7 +367,7 @@ impl DummyClient {
|
|||
uri: Url,
|
||||
line: u32,
|
||||
col: u32,
|
||||
) -> Result<Vec<Location>, Box<dyn std::error::Error>> {
|
||||
) -> Result<Location, Box<dyn std::error::Error>> {
|
||||
let params = GotoDefinitionParams {
|
||||
text_document_position_params: abs_pos(uri, line, col),
|
||||
work_done_progress_params: Default::default(),
|
||||
|
@ -380,7 +380,7 @@ impl DummyClient {
|
|||
"params": params,
|
||||
});
|
||||
self.server.dispatch(msg)?;
|
||||
self.wait_for::<Vec<Location>>()
|
||||
self.wait_for::<Location>()
|
||||
}
|
||||
|
||||
fn request_folding_range(
|
||||
|
@ -540,9 +540,8 @@ fn test_goto_definition() -> Result<(), Box<dyn std::error::Error>> {
|
|||
client.request_initialize()?;
|
||||
let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?;
|
||||
client.notify_open(FILE_A)?;
|
||||
let locations = client.request_goto_definition(uri.raw(), 1, 4)?;
|
||||
assert_eq!(locations.len(), 1);
|
||||
assert_eq!(&locations[0].range, &single_range(0, 0, 1));
|
||||
let location = client.request_goto_definition(uri.raw(), 1, 4)?;
|
||||
assert_eq!(&location.range, &single_range(0, 0, 1));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue