feat: implement smart completion

This commit is contained in:
Shunsuke Shibayama 2023-02-27 13:42:19 +09:00
parent af575aba6c
commit e57795d515
3 changed files with 100 additions and 50 deletions

View file

@ -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() => {

View file

@ -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
}

View file

@ -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) => {