mirror of
https://github.com/erg-lang/erg.git
synced 2025-07-24 13:34:52 +00:00
fix(els): signature help
This commit is contained in:
parent
1332b009b7
commit
b746cd38bd
8 changed files with 148 additions and 56 deletions
|
@ -543,7 +543,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|||
.and_then(|(token, expr)| match expr {
|
||||
Expr::Call(call) => {
|
||||
let sig_t = call.obj.t();
|
||||
let nth = self.nth(&uri, call.args.loc(), &token);
|
||||
let nth = self.nth(&uri, &call, pos);
|
||||
let additional = if matches!(token.kind, Comma) { 1 } else { 0 };
|
||||
let nth = nth + additional;
|
||||
sig_t.non_default_params()?.get(nth).cloned()
|
||||
|
|
|
@ -162,6 +162,9 @@ impl FileCache {
|
|||
pos: Position,
|
||||
offset: isize,
|
||||
) -> Option<Token> {
|
||||
if offset == 0 {
|
||||
return self.get_token(uri, pos);
|
||||
}
|
||||
let _ = self.load_once(uri);
|
||||
let ent = self.files.borrow_mut();
|
||||
let tokens = ent.get(uri)?.token_stream.as_ref()?;
|
||||
|
|
|
@ -10,6 +10,21 @@ use lsp_types::Position;
|
|||
use crate::file_cache::FileCache;
|
||||
use crate::util::{self, NormalizedUrl};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ExprKind {
|
||||
Call,
|
||||
Expr,
|
||||
}
|
||||
|
||||
impl ExprKind {
|
||||
pub const fn matches(&self, expr: &Expr) -> bool {
|
||||
matches!(
|
||||
(self, expr),
|
||||
(ExprKind::Call, Expr::Call(_)) | (ExprKind::Expr, _)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct provides:
|
||||
/// * namespace where the cursor is located (`get_namespace`)
|
||||
/// * cursor(`Token`) -> `Expr` mapping (`get_min_expr`)
|
||||
|
@ -19,6 +34,7 @@ pub struct HIRVisitor<'a> {
|
|||
file_cache: &'a FileCache,
|
||||
uri: NormalizedUrl,
|
||||
strict_cmp: bool,
|
||||
search: ExprKind,
|
||||
}
|
||||
|
||||
impl<'a> HIRVisitor<'a> {
|
||||
|
@ -32,6 +48,22 @@ impl<'a> HIRVisitor<'a> {
|
|||
file_cache,
|
||||
uri,
|
||||
strict_cmp: ERG_MODE,
|
||||
search: ExprKind::Expr,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_searcher(
|
||||
hir: MappedRwLockReadGuard<'a, HIR>,
|
||||
file_cache: &'a FileCache,
|
||||
uri: NormalizedUrl,
|
||||
search: ExprKind,
|
||||
) -> Self {
|
||||
Self {
|
||||
hir,
|
||||
file_cache,
|
||||
uri,
|
||||
strict_cmp: ERG_MODE,
|
||||
search,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,6 +208,9 @@ impl<'a> HIRVisitor<'a> {
|
|||
}
|
||||
|
||||
fn return_expr_if_same<'e>(&'e self, expr: &'e Expr, l: &Token, r: &Token) -> Option<&Expr> {
|
||||
if !self.search.matches(expr) {
|
||||
return None;
|
||||
}
|
||||
if self.strict_cmp {
|
||||
if l.deep_eq(r) {
|
||||
Some(expr)
|
||||
|
@ -189,6 +224,15 @@ impl<'a> HIRVisitor<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn return_expr_if_contains<'e>(
|
||||
&'e self,
|
||||
expr: &'e Expr,
|
||||
token: &Token,
|
||||
loc: &impl Locational,
|
||||
) -> Option<&Expr> {
|
||||
(loc.loc().contains(token.loc()) && self.search.matches(expr)).then_some(expr)
|
||||
}
|
||||
|
||||
fn get_expr<'e>(&'e self, expr: &'e Expr, token: &Token) -> Option<&'e Expr> {
|
||||
if expr.ln_end() < token.ln_begin() || expr.ln_begin() > token.ln_end() {
|
||||
return None;
|
||||
|
@ -236,7 +280,7 @@ impl<'a> HIRVisitor<'a> {
|
|||
bin: &'e BinOp,
|
||||
token: &Token,
|
||||
) -> Option<&Expr> {
|
||||
if &bin.op == token {
|
||||
if &bin.op == token && self.search.matches(expr) {
|
||||
return Some(expr);
|
||||
}
|
||||
self.get_expr(&bin.lhs, token)
|
||||
|
@ -255,13 +299,22 @@ impl<'a> HIRVisitor<'a> {
|
|||
.as_ref()
|
||||
.and_then(|(_, end)| self.return_expr_if_same(expr, end, token))
|
||||
.or_else(|| {
|
||||
call.attr_name
|
||||
.as_ref()
|
||||
.and_then(|attr| self.return_expr_if_same(expr, attr.raw.name.token(), token))
|
||||
call.attr_name.as_ref().and_then(|attr| {
|
||||
self.return_expr_if_same(expr, attr.raw.name.token(), token)
|
||||
.map(|e| {
|
||||
crate::_log!("{e}");
|
||||
e
|
||||
})
|
||||
})
|
||||
})
|
||||
.or_else(|| self.get_expr(&call.obj, token))
|
||||
.or_else(|| self.get_expr_from_args(&call.args, token))
|
||||
.or_else(|| call.loc().contains(token.loc()).then_some(expr))
|
||||
.or_else(|| {
|
||||
self.get_expr_from_args(&call.args, token).map(|e| {
|
||||
crate::_log!("{:?} / {e}", self.search);
|
||||
e
|
||||
})
|
||||
})
|
||||
.or_else(|| self.return_expr_if_contains(expr, token, call))
|
||||
}
|
||||
|
||||
fn get_expr_from_args<'e>(&'e self, args: &'e Args, token: &Token) -> Option<&Expr> {
|
||||
|
@ -291,7 +344,7 @@ impl<'a> HIRVisitor<'a> {
|
|||
) -> Option<&Expr> {
|
||||
self.return_expr_if_same(expr, def.sig.ident().raw.name.token(), token)
|
||||
.or_else(|| self.get_expr_from_block(&def.body.block, token))
|
||||
.or_else(|| def.loc().contains(token.loc()).then_some(expr))
|
||||
.or_else(|| self.return_expr_if_contains(expr, token, def))
|
||||
}
|
||||
|
||||
fn get_expr_from_class_def<'e>(
|
||||
|
@ -305,7 +358,7 @@ impl<'a> HIRVisitor<'a> {
|
|||
.as_ref()
|
||||
.and_then(|req_sup| self.get_expr(req_sup, token))
|
||||
.or_else(|| self.get_expr_from_block(&class_def.methods, token))
|
||||
.or_else(|| class_def.loc().contains(token.loc()).then_some(expr))
|
||||
.or_else(|| self.return_expr_if_contains(expr, token, class_def))
|
||||
}
|
||||
|
||||
fn get_expr_from_block<'e>(&'e self, block: &'e Block, token: &Token) -> Option<&Expr> {
|
||||
|
@ -345,7 +398,7 @@ impl<'a> HIRVisitor<'a> {
|
|||
self.return_expr_if_same(expr, patch_def.sig.name().token(), token)
|
||||
.or_else(|| self.get_expr(&patch_def.base, token))
|
||||
.or_else(|| self.get_expr_from_block(&patch_def.methods, token))
|
||||
.or_else(|| patch_def.loc().contains(token.loc()).then_some(expr))
|
||||
.or_else(|| self.return_expr_if_contains(expr, token, patch_def))
|
||||
}
|
||||
|
||||
fn get_expr_from_lambda<'e>(
|
||||
|
@ -354,7 +407,9 @@ impl<'a> HIRVisitor<'a> {
|
|||
lambda: &'e Lambda,
|
||||
token: &Token,
|
||||
) -> Option<&Expr> {
|
||||
if util::pos_in_loc(&lambda.params, util::loc_to_pos(token.loc())?) {
|
||||
if util::pos_in_loc(&lambda.params, util::loc_to_pos(token.loc())?)
|
||||
&& self.search.matches(expr)
|
||||
{
|
||||
return Some(expr);
|
||||
}
|
||||
self.get_expr_from_block(&lambda.body, token)
|
||||
|
@ -366,7 +421,7 @@ impl<'a> HIRVisitor<'a> {
|
|||
arr: &'e Array,
|
||||
token: &Token,
|
||||
) -> Option<&Expr> {
|
||||
if arr.ln_end() == token.ln_end() {
|
||||
if arr.ln_end() == token.ln_end() && self.search.matches(expr) {
|
||||
// arr: `[1, 2]`, token: `]`
|
||||
return Some(expr);
|
||||
}
|
||||
|
@ -382,7 +437,7 @@ impl<'a> HIRVisitor<'a> {
|
|||
dict: &'e Dict,
|
||||
token: &Token,
|
||||
) -> Option<&Expr> {
|
||||
if dict.ln_end() == token.ln_end() {
|
||||
if dict.ln_end() == token.ln_end() && self.search.matches(expr) {
|
||||
// arr: `{...}`, token: `}`
|
||||
return Some(expr);
|
||||
}
|
||||
|
@ -408,7 +463,7 @@ impl<'a> HIRVisitor<'a> {
|
|||
record: &'e Record,
|
||||
token: &Token,
|
||||
) -> Option<&Expr> {
|
||||
if record.ln_end() == token.ln_end() {
|
||||
if record.ln_end() == token.ln_end() && self.search.matches(expr) {
|
||||
// arr: `{...}`, token: `}`
|
||||
return Some(expr);
|
||||
}
|
||||
|
@ -426,7 +481,7 @@ impl<'a> HIRVisitor<'a> {
|
|||
set: &'e Set,
|
||||
token: &Token,
|
||||
) -> Option<&Expr> {
|
||||
if set.ln_end() == token.ln_end() {
|
||||
if set.ln_end() == token.ln_end() && self.search.matches(expr) {
|
||||
// arr: `{...}`, token: `}`
|
||||
return Some(expr);
|
||||
}
|
||||
|
@ -537,18 +592,18 @@ impl<'a> HIRVisitor<'a> {
|
|||
|
||||
fn get_args_info(&self, args: &Args, token: &Token) -> Option<VarInfo> {
|
||||
for arg in args.pos_args.iter() {
|
||||
if let Some(expr) = self.get_expr_info(&arg.expr, token) {
|
||||
return Some(expr);
|
||||
if let Some(vi) = self.get_expr_info(&arg.expr, token) {
|
||||
return Some(vi);
|
||||
}
|
||||
}
|
||||
if let Some(var) = &args.var_args {
|
||||
if let Some(expr) = self.get_expr_info(&var.expr, token) {
|
||||
return Some(expr);
|
||||
if let Some(vi) = self.get_expr_info(&var.expr, token) {
|
||||
return Some(vi);
|
||||
}
|
||||
}
|
||||
for arg in args.kw_args.iter() {
|
||||
if let Some(expr) = self.get_expr_info(&arg.expr, token) {
|
||||
return Some(expr);
|
||||
if let Some(vi) = self.get_expr_info(&arg.expr, token) {
|
||||
return Some(vi);
|
||||
}
|
||||
}
|
||||
None
|
||||
|
|
|
@ -20,7 +20,7 @@ use erg_compiler::build_hir::HIRBuilder;
|
|||
use erg_compiler::context::{Context, ModuleContext};
|
||||
use erg_compiler::erg_parser::ast::Module;
|
||||
use erg_compiler::erg_parser::parse::{Parsable, SimpleParser};
|
||||
use erg_compiler::hir::{Expr, HIR};
|
||||
use erg_compiler::hir::HIR;
|
||||
use erg_compiler::lower::ASTLowerer;
|
||||
use erg_compiler::module::{SharedCompilerResource, SharedModuleGraph, SharedModuleIndex};
|
||||
use erg_compiler::ty::HasType;
|
||||
|
@ -48,7 +48,7 @@ use serde_json::Value;
|
|||
use crate::channels::{SendChannels, Sendable, WorkerMessage};
|
||||
use crate::completion::CompletionCache;
|
||||
use crate::file_cache::FileCache;
|
||||
use crate::hir_visitor::HIRVisitor;
|
||||
use crate::hir_visitor::{ExprKind, HIRVisitor};
|
||||
use crate::message::{ErrorMessage, LSPResult, LogMessage, ShowMessage};
|
||||
use crate::util::{self, NormalizedUrl};
|
||||
|
||||
|
@ -323,7 +323,6 @@ pub struct Server<Checker: BuildRunnable = HIRBuilder, Parser: Parsable = Simple
|
|||
// TODO: remove modules, analysis_result, and add `shared: SharedCompilerResource`
|
||||
pub(crate) modules: ModuleCache,
|
||||
pub(crate) analysis_result: AnalysisResultCache,
|
||||
pub(crate) current_sig: Option<Expr>,
|
||||
pub(crate) channels: Option<SendChannels>,
|
||||
pub(crate) _parser: std::marker::PhantomData<fn() -> Parser>,
|
||||
pub(crate) _checker: std::marker::PhantomData<fn() -> Checker>,
|
||||
|
@ -343,7 +342,6 @@ impl<C: BuildRunnable, P: Parsable> Clone for Server<C, P> {
|
|||
comp_cache: self.comp_cache.clone(),
|
||||
modules: self.modules.clone(),
|
||||
analysis_result: self.analysis_result.clone(),
|
||||
current_sig: self.current_sig.clone(),
|
||||
channels: self.channels.clone(),
|
||||
_parser: std::marker::PhantomData,
|
||||
_checker: std::marker::PhantomData,
|
||||
|
@ -365,7 +363,6 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|||
file_cache: FileCache::new(),
|
||||
modules: ModuleCache::new(),
|
||||
analysis_result: AnalysisResultCache::new(),
|
||||
current_sig: None,
|
||||
channels: None,
|
||||
_parser: std::marker::PhantomData,
|
||||
_checker: std::marker::PhantomData,
|
||||
|
@ -592,7 +589,6 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|||
self.comp_cache.clear();
|
||||
self.modules = ModuleCache::new();
|
||||
self.analysis_result = AnalysisResultCache::new();
|
||||
self.current_sig = None;
|
||||
self.channels.as_ref().unwrap().close();
|
||||
self.start_language_services();
|
||||
}
|
||||
|
@ -833,6 +829,12 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|||
.map(|hir| HIRVisitor::new(hir, &self.file_cache, uri.clone()))
|
||||
}
|
||||
|
||||
pub(crate) fn get_searcher(&self, uri: &NormalizedUrl, kind: ExprKind) -> Option<HIRVisitor> {
|
||||
self.analysis_result
|
||||
.get_hir(uri)
|
||||
.map(|hir| HIRVisitor::new_searcher(hir, &self.file_cache, uri.clone(), kind))
|
||||
}
|
||||
|
||||
pub(crate) fn get_local_ctx(&self, uri: &NormalizedUrl, pos: Position) -> Vec<&Context> {
|
||||
let mut ctxs = vec![];
|
||||
if let Some(mod_ctx) = &self.modules.get(uri) {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use erg_common::traits::{DequeStream, LimitedDisplay, Locational, NoTypeDisplay};
|
||||
use erg_compiler::artifact::BuildRunnable;
|
||||
use erg_compiler::erg_parser::parse::Parsable;
|
||||
use erg_compiler::erg_parser::token::{Token, TokenKind};
|
||||
use erg_compiler::hir::Expr;
|
||||
use erg_compiler::erg_parser::token::{Token, TokenCategory, TokenKind};
|
||||
use erg_compiler::hir::{Call, Expr};
|
||||
use erg_compiler::ty::{HasType, ParamTy};
|
||||
|
||||
use lsp_types::{
|
||||
|
@ -10,8 +10,9 @@ use lsp_types::{
|
|||
SignatureHelpParams, SignatureHelpTriggerKind, SignatureInformation,
|
||||
};
|
||||
|
||||
use crate::hir_visitor::ExprKind;
|
||||
use crate::server::{send_log, ELSResult, Server};
|
||||
use crate::util::NormalizedUrl;
|
||||
use crate::util::{pos_to_loc, NormalizedUrl};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Trigger {
|
||||
|
@ -84,35 +85,58 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|||
let token = self.file_cache.get_token_relatively(uri, pos, offset)?;
|
||||
crate::_log!("token: {token}");
|
||||
if let Some(visitor) = self.get_visitor(uri) {
|
||||
#[allow(clippy::single_match)]
|
||||
match visitor.get_min_expr(&token) {
|
||||
Some(expr) => {
|
||||
return Some((token, expr.clone()));
|
||||
}
|
||||
_ => {}
|
||||
if let Some(expr) = visitor.get_min_expr(&token) {
|
||||
return Some((token, expr.clone()));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn nth(
|
||||
pub(crate) fn get_min_call(
|
||||
&self,
|
||||
uri: &NormalizedUrl,
|
||||
args_loc: erg_common::error::Location,
|
||||
token: &Token,
|
||||
) -> usize {
|
||||
pos: Position,
|
||||
offset: isize,
|
||||
) -> Option<(Token, Expr)> {
|
||||
let token = self.file_cache.get_token_relatively(uri, pos, offset)?;
|
||||
crate::_log!("token: {token}");
|
||||
if let Some(visitor) = self.get_searcher(uri, ExprKind::Call) {
|
||||
if let Some(expr) = visitor.get_min_expr(&token) {
|
||||
return Some((token, expr.clone()));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn nth(&self, uri: &NormalizedUrl, call: &Call, pos: Position) -> usize {
|
||||
let origin_loc = call
|
||||
.args
|
||||
.paren
|
||||
.as_ref()
|
||||
.map(|(l, _)| l.loc())
|
||||
.unwrap_or_else(|| call.obj.loc());
|
||||
let loc = pos_to_loc(pos);
|
||||
let tks = self.file_cache.get_token_stream(uri).unwrap_or_default();
|
||||
let mut paren = 0usize;
|
||||
// we should use the latest commas
|
||||
let commas = tks
|
||||
.iter()
|
||||
.skip_while(|&tk| tk.loc() < args_loc)
|
||||
.filter(|tk| tk.is(TokenKind::Comma) && args_loc.ln_end() >= tk.ln_begin())
|
||||
.skip_while(|&tk| tk.loc() <= origin_loc)
|
||||
.filter(|tk| {
|
||||
// skip `,` of [1, ...]
|
||||
match tk.category() {
|
||||
TokenCategory::LEnclosure => {
|
||||
paren += 1;
|
||||
}
|
||||
TokenCategory::REnclosure => {
|
||||
paren = paren.saturating_sub(1);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
paren == 0 && tk.is(TokenKind::Comma) && tk.loc() <= loc
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let argc = commas.len();
|
||||
commas
|
||||
.iter()
|
||||
.position(|c| c.col_end() >= token.col_end())
|
||||
.unwrap_or(argc) // `commas.col_end() < token.col_end()` means the token is the last argument
|
||||
commas.len()
|
||||
}
|
||||
|
||||
fn resend_help(
|
||||
|
@ -123,13 +147,14 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|||
) -> Option<SignatureHelp> {
|
||||
if let Some(token) = self.file_cache.get_token(uri, pos) {
|
||||
crate::_log!("token: {token}");
|
||||
if let Some(Expr::Call(call)) = &self.current_sig {
|
||||
if let Some((_, Expr::Call(call))) = self.get_min_call(uri, pos, 0) {
|
||||
if call.ln_begin() > token.ln_begin() || call.ln_end() < token.ln_end() {
|
||||
self.current_sig = None;
|
||||
return None;
|
||||
}
|
||||
let nth = self.nth(uri, call.args.loc(), &token) as u32;
|
||||
let nth = self.nth(uri, &call, pos) as u32;
|
||||
return self.make_sig_help(call.obj.as_ref(), nth);
|
||||
} else {
|
||||
crate::_log!("failed to get the call");
|
||||
}
|
||||
} else {
|
||||
crate::_log!("failed to get the token");
|
||||
|
@ -147,10 +172,9 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|||
}
|
||||
|
||||
fn get_continuous_help(&mut self, uri: &NormalizedUrl, pos: Position) -> Option<SignatureHelp> {
|
||||
if let Some((comma, Expr::Call(call))) = self.get_min_expr(uri, pos, -1) {
|
||||
let nth = self.nth(uri, call.args.loc(), &comma) as u32 + 1;
|
||||
if let Some((_, Expr::Call(call))) = self.get_min_call(uri, pos, -1) {
|
||||
let nth = self.nth(uri, &call, pos) as u32 + 1;
|
||||
let help = self.make_sig_help(call.obj.as_ref(), nth);
|
||||
self.current_sig = Some(Expr::Call(call));
|
||||
return help;
|
||||
} else {
|
||||
crate::_log!("failed to get continuous help");
|
||||
|
|
|
@ -96,7 +96,7 @@ pub(crate) fn loc_to_pos(loc: erg_common::error::Location) -> Option<Position> {
|
|||
Some(start)
|
||||
}
|
||||
|
||||
pub fn _pos_to_loc(pos: Position) -> erg_common::error::Location {
|
||||
pub fn pos_to_loc(pos: Position) -> erg_common::error::Location {
|
||||
erg_common::error::Location::range(
|
||||
pos.line + 1,
|
||||
pos.character.saturating_sub(1),
|
||||
|
|
|
@ -314,9 +314,9 @@ impl Ord for Location {
|
|||
} else if self.ln_begin() == self.ln_end() && other.ln_begin() == other.ln_end() {
|
||||
// assert_eq!(self.line_begin, other.line_begin);
|
||||
// assert_eq!(self.line_end, other.line_end);
|
||||
if self.col_end() < other.col_begin() {
|
||||
if self.col_end() <= other.col_begin() {
|
||||
Ordering::Less
|
||||
} else if other.col_end() < self.col_begin() {
|
||||
} else if other.col_end() <= self.col_begin() {
|
||||
Ordering::Greater
|
||||
} else {
|
||||
Ordering::Equal
|
||||
|
|
|
@ -332,6 +332,14 @@ impl Args {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn last(&self) -> Option<&Expr> {
|
||||
if self.kw_args.is_empty() {
|
||||
self.pos_args.last().map(|a| &a.expr)
|
||||
} else {
|
||||
self.kw_args.last().map(|a| &a.expr)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_left_or_key(&mut self, key: &str) -> Option<Expr> {
|
||||
if !self.pos_args.is_empty() {
|
||||
Some(self.pos_args.remove(0).expr)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue