mirror of
https://github.com/erg-lang/erg.git
synced 2025-08-04 02:39:20 +00:00
feat: implement smart completion
This commit is contained in:
parent
af575aba6c
commit
e57795d515
3 changed files with 100 additions and 50 deletions
|
@ -7,8 +7,10 @@ use erg_common::impl_u8_enum;
|
|||
use erg_common::traits::Locational;
|
||||
|
||||
use erg_compiler::artifact::BuildRunnable;
|
||||
use erg_compiler::context::Context;
|
||||
use erg_compiler::erg_parser::token::TokenKind;
|
||||
use erg_compiler::ty::Type;
|
||||
use erg_compiler::hir::Expr;
|
||||
use erg_compiler::ty::{HasType, ParamTy, Type};
|
||||
use erg_compiler::varinfo::AbsLocation;
|
||||
use erg_compiler::AccessKind;
|
||||
|
||||
|
@ -37,34 +39,66 @@ fn markdown_order(block: &str) -> usize {
|
|||
|
||||
impl_u8_enum! { CompletionOrder; i32;
|
||||
TypeMatched = -8,
|
||||
SingularAttr = -2,
|
||||
NameMatched = -2,
|
||||
Normal = 1000000,
|
||||
Builtin = 1,
|
||||
Escaped = 4,
|
||||
DoubleEscaped = 16,
|
||||
}
|
||||
|
||||
impl CompletionOrder {
|
||||
pub fn score(vi: &VarInfo, label: &str) -> i32 {
|
||||
let mut orders = vec![Self::Normal];
|
||||
if label.starts_with("__") {
|
||||
orders.push(Self::DoubleEscaped);
|
||||
} else if label.starts_with('_') {
|
||||
orders.push(Self::Escaped);
|
||||
pub struct CompletionOrderSetter<'b> {
|
||||
vi: &'b VarInfo,
|
||||
arg_pt: Option<&'b ParamTy>,
|
||||
mod_ctx: &'b Context,
|
||||
label: String,
|
||||
}
|
||||
|
||||
impl<'b> CompletionOrderSetter<'b> {
|
||||
pub fn new(
|
||||
vi: &'b VarInfo,
|
||||
arg_pt: Option<&'b ParamTy>,
|
||||
mod_ctx: &'b Context,
|
||||
label: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
vi,
|
||||
arg_pt,
|
||||
mod_ctx,
|
||||
label,
|
||||
}
|
||||
if vi.kind.is_builtin() {
|
||||
orders.push(Self::Builtin);
|
||||
}
|
||||
|
||||
pub fn score(&self) -> i32 {
|
||||
let mut orders = vec![CompletionOrder::Normal];
|
||||
if self.label.starts_with("__") {
|
||||
orders.push(CompletionOrder::DoubleEscaped);
|
||||
} else if self.label.starts_with('_') {
|
||||
orders.push(CompletionOrder::Escaped);
|
||||
}
|
||||
if self.vi.kind.is_builtin() {
|
||||
orders.push(CompletionOrder::Builtin);
|
||||
}
|
||||
if self
|
||||
.arg_pt
|
||||
.map_or(false, |pt| pt.name().map(|s| &s[..]) == Some(&self.label))
|
||||
{
|
||||
orders.push(CompletionOrder::NameMatched);
|
||||
}
|
||||
if self.arg_pt.map_or(false, |pt| {
|
||||
self.mod_ctx.subtype_of(&self.vi.t, pt.typ(), true)
|
||||
}) {
|
||||
orders.push(CompletionOrder::TypeMatched);
|
||||
}
|
||||
orders.into_iter().map(i32::from).sum()
|
||||
}
|
||||
|
||||
pub fn mangle(vi: &VarInfo, label: &str) -> String {
|
||||
let score = Self::score(vi, label);
|
||||
format!("{}_{}", char::from_u32(score as u32).unwrap(), label)
|
||||
pub fn mangle(&self) -> String {
|
||||
let score = self.score();
|
||||
format!("{}_{}", char::from_u32(score as u32).unwrap(), self.label)
|
||||
}
|
||||
|
||||
fn set(vi: &VarInfo, item: &mut CompletionItem) {
|
||||
item.sort_text = Some(Self::mangle(vi, &item.label));
|
||||
fn set(&self, item: &mut CompletionItem) {
|
||||
item.sort_text = Some(self.mangle());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,6 +135,16 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
} else {
|
||||
self.get_receiver_ctxs(&uri, pos)?
|
||||
};
|
||||
let arg_pt = self.get_min_expr(&uri, pos, -1).and_then(|(token, expr)| {
|
||||
if let Expr::Call(call) = expr {
|
||||
let sig_t = call.obj.t();
|
||||
let nth = self.nth(&uri, call.args.loc(), &token);
|
||||
sig_t.non_default_params()?.get(nth).cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let mod_ctx = &self.modules.get(&uri).unwrap().context;
|
||||
for (name, vi) in contexts.into_iter().flat_map(|ctx| ctx.dir()) {
|
||||
if acc_kind.is_attr() && vi.vis.is_private() {
|
||||
continue;
|
||||
|
@ -128,7 +172,8 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
})
|
||||
.unwrap_or_else(|| vi.t.clone());
|
||||
let mut item = CompletionItem::new_simple(name.to_string(), readable_t.to_string());
|
||||
CompletionOrder::set(vi, &mut item);
|
||||
CompletionOrderSetter::new(vi, arg_pt.as_ref(), mod_ctx, item.label.clone())
|
||||
.set(&mut item);
|
||||
item.kind = match &vi.t {
|
||||
Type::Subr(subr) if subr.self_t().is_some() => Some(CompletionItemKind::METHOD),
|
||||
Type::Quantified(quant) if quant.self_t().is_some() => {
|
||||
|
|
|
@ -64,7 +64,35 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
send(&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": result }))
|
||||
}
|
||||
|
||||
fn nth(&self, uri: &Url, args_loc: erg_common::error::Location, token: &Token) -> usize {
|
||||
pub(crate) fn get_min_expr(
|
||||
&self,
|
||||
uri: &Url,
|
||||
pos: Position,
|
||||
offset: isize,
|
||||
) -> Option<(Token, Expr)> {
|
||||
let token = self
|
||||
.file_cache
|
||||
.get_token_relatively(uri, pos, offset)
|
||||
.ok()??;
|
||||
send_log(format!("token: {token}")).unwrap();
|
||||
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()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn nth(
|
||||
&self,
|
||||
uri: &Url,
|
||||
args_loc: erg_common::error::Location,
|
||||
token: &Token,
|
||||
) -> usize {
|
||||
// we should use the latest commas
|
||||
let commas = self
|
||||
.file_cache
|
||||
|
@ -104,20 +132,8 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
}
|
||||
|
||||
fn get_first_help(&mut self, uri: &Url, pos: Position) -> Option<SignatureHelp> {
|
||||
if let Some(token) = self.file_cache.get_token_relatively(uri, pos, -2).ok()? {
|
||||
// send_log(format!("token before `(`: {token}")).unwrap();
|
||||
if let Some(visitor) = self.get_visitor(uri) {
|
||||
match visitor.get_min_expr(&token) {
|
||||
Some(Expr::Call(_call)) => {
|
||||
// let sig_t = call.signature_t().unwrap();
|
||||
// send_log(format!("call: {call}")).unwrap();
|
||||
}
|
||||
Some(Expr::Accessor(acc)) => {
|
||||
return self.make_sig_help(acc, 0);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some((_token, Expr::Accessor(acc))) = self.get_min_expr(uri, pos, -2) {
|
||||
return self.make_sig_help(&acc, 0);
|
||||
} else {
|
||||
send_log("lex error occurred").unwrap();
|
||||
}
|
||||
|
@ -125,24 +141,13 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
}
|
||||
|
||||
fn get_continuous_help(&mut self, uri: &Url, pos: Position) -> Option<SignatureHelp> {
|
||||
if let Some(comma) = self.file_cache.get_token_relatively(uri, pos, -1).ok()? {
|
||||
send_log(format!("comma: {comma}")).unwrap();
|
||||
if let Some(visitor) = self.get_visitor(uri) {
|
||||
#[allow(clippy::single_match)]
|
||||
match visitor.get_min_expr(&comma) {
|
||||
Some(Expr::Call(call)) => {
|
||||
let nth = self.nth(uri, call.args.loc(), &comma) as u32 + 1;
|
||||
let help = self.make_sig_help(call.obj.as_ref(), nth);
|
||||
self.current_sig = Some(Expr::Call(call.clone()));
|
||||
return help;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
// send_log("visitor not found").unwrap();
|
||||
}
|
||||
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;
|
||||
let help = self.make_sig_help(call.obj.as_ref(), nth);
|
||||
self.current_sig = Some(Expr::Call(call));
|
||||
return help;
|
||||
} else {
|
||||
send_log("lex error occurred").unwrap();
|
||||
send_log("failed to get continuous help").unwrap();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
|
@ -153,7 +153,7 @@ impl Context {
|
|||
/// Seq(T) :> Range(T)
|
||||
/// => Range(T).super_types == [Eq, Mutate, Seq(T), Output(T)]
|
||||
/// ```
|
||||
pub(crate) fn subtype_of(&self, lhs: &Type, rhs: &Type, allow_cast: bool) -> bool {
|
||||
pub fn subtype_of(&self, lhs: &Type, rhs: &Type, allow_cast: bool) -> bool {
|
||||
match Self::cheap_subtype_of(lhs, rhs, allow_cast) {
|
||||
(Absolutely, judge) => judge,
|
||||
(Maybe, judge) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue