mirror of
https://github.com/erg-lang/erg.git
synced 2025-08-04 10:49:54 +00:00
898 lines
33 KiB
Rust
898 lines
33 KiB
Rust
use std::path::Path;
|
|
|
|
use erg_compiler::erg_parser::ast::Identifier;
|
|
use serde_json::Value;
|
|
|
|
use erg_common::config::ErgConfig;
|
|
use erg_common::consts::{ERG_MODE, PYTHON_MODE};
|
|
use erg_common::dict::Dict;
|
|
use erg_common::env::erg_pystd_path;
|
|
use erg_common::impl_u8_enum;
|
|
use erg_common::io::Input;
|
|
use erg_common::python_util::{BUILTIN_PYTHON_MODS, EXT_COMMON_ALIAS, EXT_PYTHON_MODS};
|
|
use erg_common::set::Set;
|
|
use erg_common::shared::{MappedRwLockReadGuard, RwLockReadGuard, Shared};
|
|
use erg_common::spawn::spawn_new_thread;
|
|
use erg_common::traits::Locational;
|
|
|
|
use erg_compiler::artifact::{BuildRunnable, Buildable};
|
|
use erg_compiler::build_package::PackageBuilder;
|
|
use erg_compiler::context::Context;
|
|
use erg_compiler::erg_parser::parse::Parsable;
|
|
use erg_compiler::erg_parser::token::TokenKind;
|
|
use erg_compiler::hir::Expr;
|
|
use erg_compiler::module::SharedCompilerResource;
|
|
use erg_compiler::ty::constructors::{poly, ty_tp};
|
|
use erg_compiler::ty::{HasType, ParamTy, Type};
|
|
use erg_compiler::varinfo::{AbsLocation, Mutability, VarInfo, VarKind};
|
|
use TokenKind::*;
|
|
|
|
use lsp_types::{
|
|
CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse, Documentation,
|
|
MarkedString, MarkupContent, MarkupKind, Position, Range, TextEdit,
|
|
};
|
|
|
|
use crate::_log;
|
|
use crate::server::{DefaultFeatures, ELSResult, Flags, RedirectableStdout, Server};
|
|
use crate::util::{self, loc_to_pos, loc_to_range, NormalizedUrl};
|
|
|
|
fn comp_item_kind(t: &Type, muty: Mutability) -> CompletionItemKind {
|
|
match t {
|
|
Type::Subr(subr) if subr.self_t().is_some() => CompletionItemKind::METHOD,
|
|
Type::Quantified(quant) if quant.self_t().is_some() => CompletionItemKind::METHOD,
|
|
Type::Subr(_) | Type::Quantified(_) => CompletionItemKind::FUNCTION,
|
|
Type::ClassType => CompletionItemKind::CLASS,
|
|
Type::TraitType => CompletionItemKind::INTERFACE,
|
|
Type::Or(tys) => {
|
|
let fst = comp_item_kind(tys.iter().next().unwrap(), muty);
|
|
if tys
|
|
.iter()
|
|
.map(|t| comp_item_kind(t, muty))
|
|
.all(|k| k == fst)
|
|
{
|
|
fst
|
|
} else if muty.is_const() {
|
|
CompletionItemKind::CONSTANT
|
|
} else {
|
|
CompletionItemKind::VARIABLE
|
|
}
|
|
}
|
|
Type::And(tys, _) => {
|
|
for k in tys.iter().map(|t| comp_item_kind(t, muty)) {
|
|
if k != CompletionItemKind::VARIABLE {
|
|
return k;
|
|
}
|
|
}
|
|
if muty.is_const() {
|
|
CompletionItemKind::CONSTANT
|
|
} else {
|
|
CompletionItemKind::VARIABLE
|
|
}
|
|
}
|
|
Type::Refinement(r) => comp_item_kind(&r.t, muty),
|
|
Type::Bounded { sub, .. } => comp_item_kind(sub, muty),
|
|
t if matches!(&t.qual_name()[..], "Module" | "PyModule" | "GenericModule") => {
|
|
CompletionItemKind::MODULE
|
|
}
|
|
Type::Type => CompletionItemKind::CONSTANT,
|
|
_ if muty.is_const() => CompletionItemKind::CONSTANT,
|
|
_ => CompletionItemKind::VARIABLE,
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub enum CompletionKind {
|
|
RetriggerLocal,
|
|
Local,
|
|
LParen,
|
|
Method,
|
|
RetriggerMethod,
|
|
// Colon, // :, Type ascription or private access `::`
|
|
}
|
|
|
|
impl CompletionKind {
|
|
pub const fn should_be_local(&self) -> bool {
|
|
matches!(self, Self::RetriggerLocal | Self::Local | Self::LParen)
|
|
}
|
|
|
|
pub const fn should_be_method(&self) -> bool {
|
|
matches!(self, Self::Method | Self::RetriggerMethod)
|
|
}
|
|
|
|
pub const fn _is_lparen(&self) -> bool {
|
|
matches!(self, Self::LParen)
|
|
}
|
|
}
|
|
|
|
fn mark_to_string(mark: MarkedString) -> String {
|
|
match mark {
|
|
MarkedString::String(s) => s,
|
|
MarkedString::LanguageString(ls) => format!("```{}\n{}\n```", ls.language, ls.value),
|
|
}
|
|
}
|
|
|
|
fn markdown_order(block: &str) -> usize {
|
|
if block.starts_with("```") {
|
|
usize::MAX
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
impl_u8_enum! { CompletionOrder; i32;
|
|
TypeMatched = -32,
|
|
NameMatched = -8,
|
|
ReturnTypeMatched = -2,
|
|
Normal = 1000000,
|
|
Builtin = 1,
|
|
OtherNamespace = 2,
|
|
PseudoMethod = 16,
|
|
Escaped = 32,
|
|
DoubleEscaped = 64,
|
|
}
|
|
|
|
impl CompletionOrder {
|
|
pub const STD_ITEM: char = match char::from_u32(
|
|
CompletionOrder::Normal as u32
|
|
+ CompletionOrder::Builtin as u32
|
|
+ CompletionOrder::OtherNamespace as u32,
|
|
) {
|
|
Some(c) => c,
|
|
None => unreachable!(),
|
|
};
|
|
}
|
|
|
|
pub struct CompletionOrderSetter<'b> {
|
|
t: &'b Type,
|
|
kind: &'b VarKind,
|
|
arg_pt: Option<&'b ParamTy>,
|
|
mod_ctx: &'b Context, // for subtype judgement, not for variable lookup
|
|
label: String,
|
|
}
|
|
|
|
impl<'b> CompletionOrderSetter<'b> {
|
|
pub fn new(
|
|
t: &'b Type,
|
|
kind: &'b VarKind,
|
|
arg_pt: Option<&'b ParamTy>,
|
|
mod_ctx: &'b Context,
|
|
label: String,
|
|
) -> Self {
|
|
Self {
|
|
t,
|
|
kind,
|
|
arg_pt,
|
|
mod_ctx,
|
|
label,
|
|
}
|
|
}
|
|
|
|
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);
|
|
} else if self.label.starts_with("Function::") {
|
|
orders.push(CompletionOrder::PseudoMethod);
|
|
}
|
|
if self.kind.is_builtin() {
|
|
orders.push(CompletionOrder::Builtin);
|
|
}
|
|
if self
|
|
.arg_pt
|
|
.is_some_and(|pt| pt.name().map(|s| &s[..]) == Some(&self.label))
|
|
{
|
|
orders.push(CompletionOrder::NameMatched);
|
|
}
|
|
#[allow(clippy::blocks_in_conditions)]
|
|
if self
|
|
.arg_pt
|
|
.is_some_and(|pt| self.mod_ctx.subtype_of(self.t, pt.typ()))
|
|
{
|
|
orders.push(CompletionOrder::TypeMatched);
|
|
} else if self.arg_pt.is_some_and(|pt| {
|
|
let Some(return_t) = self.t.return_t() else {
|
|
return false;
|
|
};
|
|
if return_t.has_qvar() {
|
|
return false;
|
|
}
|
|
self.mod_ctx.subtype_of(return_t, pt.typ())
|
|
}) {
|
|
orders.push(CompletionOrder::ReturnTypeMatched);
|
|
}
|
|
orders.into_iter().map(i32::from).sum()
|
|
}
|
|
|
|
pub fn mangle(&self) -> String {
|
|
let score = self.score();
|
|
format!(
|
|
"{}_{}",
|
|
char::from_u32(score as u32).unwrap_or(CompletionOrder::STD_ITEM),
|
|
self.label
|
|
)
|
|
}
|
|
|
|
fn set(&self, item: &mut CompletionItem) {
|
|
item.sort_text = Some(self.mangle());
|
|
}
|
|
}
|
|
|
|
type Cache = Shared<Dict<String, Vec<CompletionItem>>>;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct CompletionCache {
|
|
cache: Cache,
|
|
}
|
|
|
|
fn external_item(name: &str, vi: &VarInfo, mod_name: &str) -> CompletionItem {
|
|
#[cfg(feature = "py_compat")]
|
|
let mod_name = mod_name.replace('/', ".");
|
|
let mut item =
|
|
CompletionItem::new_simple(format!("{name} (import from {mod_name})"), vi.t.to_string());
|
|
item.sort_text = Some(format!("{}_{}", CompletionOrder::STD_ITEM, item.label));
|
|
item.kind = Some(comp_item_kind(&vi.t, vi.muty));
|
|
let import = if PYTHON_MODE {
|
|
format!("from {mod_name} import {name}\n")
|
|
} else {
|
|
format!("{{{name};}} = pyimport \"{mod_name}\"\n")
|
|
};
|
|
item.additional_text_edits = Some(vec![TextEdit {
|
|
range: Range::new(Position::new(0, 0), Position::new(0, 0)),
|
|
new_text: import,
|
|
}]);
|
|
item.insert_text = Some(name.trim_end_matches('\0').to_string());
|
|
item.filter_text = Some(name.to_string());
|
|
item
|
|
}
|
|
|
|
fn module_item(name: &str, mistype: bool, insert: Option<u32>) -> CompletionItem {
|
|
let mut item =
|
|
CompletionItem::new_simple(format!("{name} (magic completion)"), "Module".to_string());
|
|
item.kind = Some(CompletionItemKind::MODULE);
|
|
// `import datetime`
|
|
// => `datetime = pyimport "datetime"`
|
|
if let Some(line) = insert {
|
|
let prefix = if mistype { "py" } else { "" };
|
|
let import = format!(
|
|
"{} = {prefix}",
|
|
name.split('/').next_back().unwrap_or("module")
|
|
);
|
|
item.additional_text_edits = Some(vec![TextEdit {
|
|
range: Range::new(Position::new(line - 1, 0), Position::new(line - 1, 0)),
|
|
new_text: import,
|
|
}]);
|
|
}
|
|
item.sort_text = mistype.then(|| format!("{}_{}", CompletionOrder::STD_ITEM, item.label));
|
|
item.insert_text = Some(format!("\"{name}\""));
|
|
item.filter_text = Some(name.to_string());
|
|
item
|
|
}
|
|
|
|
fn module_completions() -> Vec<CompletionItem> {
|
|
let mut comps = Vec::with_capacity(BUILTIN_PYTHON_MODS.len());
|
|
for mod_name in BUILTIN_PYTHON_MODS.into_iter() {
|
|
let mut item = CompletionItem::new_simple(
|
|
format!("{mod_name} (import from std)"),
|
|
"PyModule".to_string(),
|
|
);
|
|
item.sort_text = Some(format!("{}_{}", CompletionOrder::STD_ITEM, item.label));
|
|
item.kind = Some(CompletionItemKind::MODULE);
|
|
let import = if PYTHON_MODE {
|
|
format!("import {mod_name}\n")
|
|
} else {
|
|
format!("{mod_name} = pyimport \"{mod_name}\"\n")
|
|
};
|
|
item.additional_text_edits = Some(vec![TextEdit {
|
|
range: Range::new(Position::new(0, 0), Position::new(0, 0)),
|
|
new_text: import,
|
|
}]);
|
|
item.insert_text = Some(mod_name.trim_end_matches('\0').to_string());
|
|
item.filter_text = Some(mod_name.to_string());
|
|
comps.push(item);
|
|
}
|
|
#[cfg(not(feature = "py_compat"))]
|
|
for mod_name in erg_common::erg_util::BUILTIN_ERG_MODS {
|
|
let mut item = CompletionItem::new_simple(
|
|
format!("{mod_name} (import from std)"),
|
|
"Module".to_string(),
|
|
);
|
|
item.sort_text = Some(format!("{}_{}", CompletionOrder::STD_ITEM, item.label));
|
|
item.kind = Some(CompletionItemKind::MODULE);
|
|
let import = format!("{mod_name} = import \"{mod_name}\"\n");
|
|
item.additional_text_edits = Some(vec![TextEdit {
|
|
range: Range::new(Position::new(0, 0), Position::new(0, 0)),
|
|
new_text: import,
|
|
}]);
|
|
item.insert_text = Some(mod_name.to_string());
|
|
item.filter_text = Some(mod_name.to_string());
|
|
comps.push(item);
|
|
}
|
|
for (mod_name, alias) in EXT_PYTHON_MODS.into_iter().zip(EXT_COMMON_ALIAS) {
|
|
let mut item = CompletionItem::new_simple(
|
|
format!("{mod_name} (external library)"),
|
|
"PyModule".to_string(),
|
|
);
|
|
item.sort_text = Some(format!("{}_{}", CompletionOrder::STD_ITEM, item.label));
|
|
item.kind = Some(CompletionItemKind::MODULE);
|
|
let import = if PYTHON_MODE {
|
|
format!("import {mod_name}\n")
|
|
} else {
|
|
format!("{mod_name} = pyimport \"{mod_name}\"\n")
|
|
};
|
|
item.additional_text_edits = Some(vec![TextEdit {
|
|
range: Range::new(Position::new(0, 0), Position::new(0, 0)),
|
|
new_text: import,
|
|
}]);
|
|
item.insert_text = Some(mod_name.trim_end_matches('\0').to_string());
|
|
item.filter_text = Some(mod_name.to_string());
|
|
comps.push(item);
|
|
if mod_name != alias {
|
|
let mut item = CompletionItem::new_simple(
|
|
format!("{alias} (external library, alias of {mod_name})"),
|
|
"PyModule".to_string(),
|
|
);
|
|
item.sort_text = Some(format!("{}_{}", CompletionOrder::STD_ITEM, item.label));
|
|
item.kind = Some(CompletionItemKind::MODULE);
|
|
let import = if PYTHON_MODE {
|
|
format!("import {mod_name} as {alias}\n")
|
|
} else {
|
|
format!("{alias} = pyimport \"{mod_name}\"\n")
|
|
};
|
|
item.additional_text_edits = Some(vec![TextEdit {
|
|
range: Range::new(Position::new(0, 0), Position::new(0, 0)),
|
|
new_text: import,
|
|
}]);
|
|
item.insert_text = Some(alias.trim_end_matches('\0').to_string());
|
|
item.filter_text = Some(mod_name.to_string());
|
|
comps.push(item);
|
|
}
|
|
}
|
|
comps
|
|
}
|
|
|
|
fn load_modules<'a>(
|
|
cfg: ErgConfig,
|
|
cache: Cache,
|
|
root: &Path,
|
|
mods: impl Iterator<Item = &'a str>,
|
|
shared: SharedCompilerResource,
|
|
) {
|
|
let src = mods.fold("".to_string(), |acc, module| {
|
|
acc + &format!("_ = pyimport \"{module}\"\n")
|
|
});
|
|
let cfg = ErgConfig {
|
|
input: Input::str(src.clone()),
|
|
..cfg
|
|
};
|
|
let mut checker = PackageBuilder::inherit(cfg, shared.clone());
|
|
let _res = checker.build(src, "exec");
|
|
let mut cache = cache.borrow_mut();
|
|
if cache.get("<module>").is_none() {
|
|
cache.insert("<module>".into(), module_completions());
|
|
}
|
|
let std_path = root.display().to_string().replace('\\', "/");
|
|
for (path, entry) in shared.py_mod_cache.ref_inner().iter() {
|
|
let dir = entry.module.context.local_dir();
|
|
let mod_name = path.display().to_string().replace('\\', "/");
|
|
let mod_name = mod_name
|
|
.trim_start_matches(&std_path)
|
|
.trim_start_matches('/')
|
|
.trim_end_matches("/__init__.d.er")
|
|
.trim_end_matches(".d.er")
|
|
.replace(".d", "");
|
|
let items = dir
|
|
.into_iter()
|
|
.filter(|(name, _)| !name.inspect().starts_with('%'))
|
|
.map(|(name, vi)| external_item(name.inspect(), vi, &mod_name));
|
|
cache.get_mut("<module>").unwrap().extend(items)
|
|
}
|
|
}
|
|
|
|
impl CompletionCache {
|
|
pub fn new(
|
|
cfg: ErgConfig,
|
|
flags: Flags,
|
|
shared: SharedCompilerResource,
|
|
external_items: bool,
|
|
) -> Self {
|
|
let cache = Shared::new(Dict::default());
|
|
let clone = cache.clone();
|
|
if external_items {
|
|
spawn_new_thread(
|
|
move || {
|
|
// crate::_log!("load_modules");
|
|
let major_mods = [
|
|
"argparse",
|
|
"array",
|
|
"asyncio",
|
|
"base64",
|
|
"datetime",
|
|
"decimal",
|
|
"fraction",
|
|
"glob",
|
|
"html",
|
|
"http",
|
|
"http/client",
|
|
"http/server",
|
|
"io",
|
|
"json",
|
|
"logging",
|
|
"math",
|
|
"os",
|
|
"os/path",
|
|
"pathlib",
|
|
"platform",
|
|
"random",
|
|
"re",
|
|
"shutil",
|
|
"socket",
|
|
"sqlite3",
|
|
"ssl",
|
|
"string",
|
|
"subprocess",
|
|
"sys",
|
|
"tempfile",
|
|
"time",
|
|
"timeit",
|
|
"unittest",
|
|
"urllib",
|
|
"zipfile",
|
|
];
|
|
#[cfg(feature = "py_compat")]
|
|
let py_specific_mods = ["dataclasses", "typing", "collections/abc"];
|
|
#[cfg(not(feature = "py_compat"))]
|
|
let py_specific_mods = [];
|
|
load_modules(
|
|
cfg.clone(),
|
|
clone.clone(),
|
|
erg_pystd_path(),
|
|
major_mods.into_iter().chain(py_specific_mods),
|
|
shared,
|
|
);
|
|
// TODO: load modules from site-packages
|
|
flags
|
|
.builtin_modules_loaded
|
|
.store(true, std::sync::atomic::Ordering::Relaxed);
|
|
},
|
|
"load_modules",
|
|
);
|
|
}
|
|
Self { cache }
|
|
}
|
|
|
|
pub fn get(&self, namespace: &str) -> Option<MappedRwLockReadGuard<Vec<CompletionItem>>> {
|
|
RwLockReadGuard::try_map(self.cache.borrow(), |cache| cache.get(namespace)).ok()
|
|
}
|
|
|
|
pub fn insert(&self, namespace: String, items: Vec<CompletionItem>) {
|
|
self.cache.borrow_mut().insert(namespace, items);
|
|
}
|
|
|
|
pub fn clear(&self) {
|
|
self.cache.borrow_mut().clear();
|
|
}
|
|
|
|
pub fn _append(&self, cache: Dict<String, Vec<CompletionItem>>) {
|
|
for (k, v) in cache {
|
|
if let Some(comps) = self.cache.borrow_mut().get_mut(&k) {
|
|
comps.extend(v);
|
|
} else {
|
|
self.cache.borrow_mut().insert(k, v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
|
|
/// Returns completion candidates from modules in the same directory
|
|
fn neighbor_completion(
|
|
&self,
|
|
uri: &NormalizedUrl,
|
|
arg_pt: Option<ParamTy>,
|
|
already_appeared: &mut Set<String>,
|
|
) -> Vec<CompletionItem> {
|
|
let mut comps = vec![];
|
|
for mod_ctx in self.get_neighbor_ctxs(uri) {
|
|
for (name, vi) in mod_ctx.local_dir() {
|
|
if vi.vis.is_private() {
|
|
continue;
|
|
}
|
|
let Some(path) = vi.def_loc.module.as_ref() else {
|
|
continue;
|
|
};
|
|
let path = path.file_stem().unwrap_or_default().to_string_lossy();
|
|
let label = format!("{name} (import from {path})");
|
|
if already_appeared.contains(&label[..]) {
|
|
continue;
|
|
}
|
|
let mut item = CompletionItem::new_simple(label, vi.t.to_string());
|
|
CompletionOrderSetter::new(
|
|
&vi.t,
|
|
&vi.kind,
|
|
arg_pt.as_ref(),
|
|
mod_ctx,
|
|
item.label.clone(),
|
|
)
|
|
.set(&mut item);
|
|
// item.sort_text = Some(format!("{}_{}", CompletionOrder::OtherNamespace, item.label));
|
|
item.kind = Some(comp_item_kind(&vi.t, vi.muty));
|
|
item.data = Some(Value::String(vi.def_loc.to_string()));
|
|
let import = if PYTHON_MODE {
|
|
format!("from {path} import {name}\n")
|
|
} else {
|
|
format!("{{{name};}} = import \"{path}\"\n")
|
|
};
|
|
item.additional_text_edits = Some(vec![TextEdit {
|
|
range: Range::new(Position::new(0, 0), Position::new(0, 0)),
|
|
new_text: import,
|
|
}]);
|
|
item.insert_text = Some(name.inspect().trim_end_matches('\0').to_string());
|
|
item.filter_text = Some(name.inspect().to_string());
|
|
already_appeared.insert(item.label.clone());
|
|
comps.push(item);
|
|
}
|
|
}
|
|
comps
|
|
}
|
|
|
|
fn kw_arg_completion(&self, sig_t: &Type, mod_ctx: &Context) -> Vec<CompletionItem> {
|
|
let mut result = vec![];
|
|
if let Some(d_params) = sig_t.default_params() {
|
|
for d_param in d_params {
|
|
let subst = if PYTHON_MODE { "=" } else { ":=" };
|
|
let mut item = CompletionItem::new_simple(
|
|
format!("{}{subst}", d_param.name().unwrap()),
|
|
d_param.typ().to_string(),
|
|
);
|
|
CompletionOrderSetter::new(
|
|
d_param.typ(),
|
|
&VarKind::Declared,
|
|
None,
|
|
mod_ctx,
|
|
item.label.clone(),
|
|
)
|
|
.set(&mut item);
|
|
item.kind = Some(comp_item_kind(d_param.typ(), Mutability::Immutable));
|
|
result.push(item);
|
|
}
|
|
}
|
|
result
|
|
}
|
|
|
|
pub(crate) fn handle_completion(
|
|
&mut self,
|
|
params: CompletionParams,
|
|
) -> ELSResult<Option<CompletionResponse>> {
|
|
_log!(self, "completion requested: {params:?}");
|
|
let uri = NormalizedUrl::new(params.text_document_position.text_document.uri);
|
|
let path = util::uri_to_path(&uri);
|
|
let mut pos = params.text_document_position.position;
|
|
// ignore comments
|
|
// TODO: multiline comments
|
|
if self
|
|
.file_cache
|
|
.get_line(&uri, pos.line)
|
|
.is_some_and(|line| line.starts_with('#'))
|
|
{
|
|
return Ok(None);
|
|
}
|
|
let trigger = params
|
|
.context
|
|
.as_ref()
|
|
.and_then(|comp_ctx| comp_ctx.trigger_character.as_ref().map(|s| &s[..]));
|
|
let comp_kind = match trigger {
|
|
Some(".") => CompletionKind::Method,
|
|
Some(":") => CompletionKind::Method,
|
|
Some(" ") => CompletionKind::Local,
|
|
Some("(") => CompletionKind::LParen,
|
|
_ => {
|
|
let offset = match self.file_cache.get_token(&uri, pos).map(|tk| tk.kind) {
|
|
Some(TokenKind::Newline | TokenKind::EOF) => -2,
|
|
_ => -1,
|
|
};
|
|
let prev_token = self.file_cache.get_token_relatively(&uri, pos, offset);
|
|
match prev_token {
|
|
Some(prev) if matches!(prev.kind, Dot | DblColon) => {
|
|
if let Some(p) = loc_to_pos(prev.loc()) {
|
|
pos = p;
|
|
}
|
|
CompletionKind::RetriggerMethod
|
|
}
|
|
_ => CompletionKind::RetriggerLocal,
|
|
}
|
|
}
|
|
};
|
|
self.send_log(format!("CompletionKind: {comp_kind:?}"))?;
|
|
let mut result: Vec<CompletionItem> = vec![];
|
|
let mut already_appeared = Set::new();
|
|
let (receiver_t, contexts) = if comp_kind.should_be_local() {
|
|
(None, self.get_local_ctx(&uri, pos))
|
|
} else {
|
|
self.get_receiver_and_ctxs(&uri, pos)?
|
|
};
|
|
let offset = match comp_kind {
|
|
CompletionKind::RetriggerLocal => 0,
|
|
CompletionKind::Method => -1,
|
|
CompletionKind::Local => -1,
|
|
CompletionKind::LParen => 0,
|
|
CompletionKind::RetriggerMethod => -1,
|
|
};
|
|
let Some(mod_ctx) = self.get_mod_ctx(&uri) else {
|
|
_log!(self, "module context not found: {uri}");
|
|
return Ok(Some(CompletionResponse::Array(result)));
|
|
};
|
|
let arg_pt = self
|
|
.get_min_expr(&uri, pos, offset)
|
|
.and_then(|(token, expr)| match expr {
|
|
Expr::Call(call) => {
|
|
let sig_t = call.signature_t().unwrap();
|
|
result.extend(self.kw_arg_completion(sig_t, &mod_ctx.context));
|
|
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()
|
|
}
|
|
other if comp_kind == CompletionKind::Local => {
|
|
match other.show_acc().as_deref() {
|
|
Some("import") => {
|
|
let insert = other
|
|
.col_begin()
|
|
.and_then(|cb| (cb == 0).then(|| other.ln_begin().unwrap_or(0)));
|
|
for erg_mod in erg_common::erg_util::BUILTIN_ERG_MODS {
|
|
result.push(module_item(erg_mod, false, insert));
|
|
}
|
|
for py_mod in BUILTIN_PYTHON_MODS {
|
|
result.push(module_item(py_mod, true, insert));
|
|
}
|
|
}
|
|
Some("pyimport") => {
|
|
let insert = other
|
|
.col_begin()
|
|
.and_then(|cb| (cb == 0).then(|| other.ln_begin().unwrap_or(0)));
|
|
for py_mod in BUILTIN_PYTHON_MODS {
|
|
result.push(module_item(py_mod, false, insert));
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
let sig_t = other.t();
|
|
sig_t.non_default_params()?.first().cloned()
|
|
}
|
|
_ => None,
|
|
});
|
|
if PYTHON_MODE {
|
|
if let Some(receiver_t) = &receiver_t {
|
|
for (field, ty) in mod_ctx.context.fields(receiver_t) {
|
|
let mut item =
|
|
CompletionItem::new_simple(field.symbol.to_string(), ty.to_string());
|
|
CompletionOrderSetter::new(
|
|
&ty,
|
|
&VarKind::Builtin,
|
|
arg_pt.as_ref(),
|
|
&mod_ctx.context,
|
|
item.label.clone(),
|
|
)
|
|
.set(&mut item);
|
|
item.kind = Some(comp_item_kind(&ty, Mutability::Immutable));
|
|
self.set_pseudo_method_comp(&uri, pos, &comp_kind, &mut item)?;
|
|
already_appeared.insert(item.label.clone());
|
|
result.push(item);
|
|
}
|
|
}
|
|
}
|
|
if let Some(receiver_t) = &receiver_t {
|
|
result.extend(self.magic_completion_items(
|
|
&comp_kind,
|
|
receiver_t,
|
|
&uri,
|
|
pos,
|
|
&mod_ctx.context,
|
|
)?);
|
|
}
|
|
if receiver_t.as_ref().is_none_or(|t| t == &Type::Never) {
|
|
let pos = params.text_document_position.position;
|
|
if let Some(attr) = self.file_cache.get_symbol(&uri, pos) {
|
|
result.extend(self.get_attr_completion_by_name(
|
|
&comp_kind,
|
|
attr.inspect(),
|
|
&mod_ctx.context,
|
|
)?);
|
|
}
|
|
}
|
|
for (name, vi) in contexts.into_iter().flat_map(|ctx| ctx.local_dir()) {
|
|
if comp_kind.should_be_method() && vi.vis.is_private() {
|
|
continue;
|
|
}
|
|
// only show static methods, if the receiver is a type
|
|
if vi.t.is_method()
|
|
&& receiver_t.as_ref().is_none_or(|receiver| {
|
|
!mod_ctx
|
|
.context
|
|
.subtype_of(receiver, vi.t.self_t().unwrap_or(Type::OBJ))
|
|
})
|
|
{
|
|
continue;
|
|
}
|
|
let label = name.inspect();
|
|
// don't show overridden items
|
|
if already_appeared.contains(&label[..]) {
|
|
continue;
|
|
}
|
|
if label.starts_with('%') {
|
|
continue;
|
|
}
|
|
let label = label.trim_end_matches('\0').to_string();
|
|
// don't show future defined items
|
|
if vi.def_loc.module.as_deref() == Some(&path)
|
|
&& name.ln_begin().unwrap_or(0) > pos.line + 1
|
|
{
|
|
continue;
|
|
}
|
|
let readable_t = mod_ctx.context.readable_type(vi.t.clone());
|
|
let mut item = CompletionItem::new_simple(label, readable_t.to_string());
|
|
CompletionOrderSetter::new(
|
|
&vi.t,
|
|
&vi.kind,
|
|
arg_pt.as_ref(),
|
|
&mod_ctx.context,
|
|
item.label.clone(),
|
|
)
|
|
.set(&mut item);
|
|
item.kind = Some(comp_item_kind(&vi.t, vi.muty));
|
|
item.data = Some(Value::String(vi.def_loc.to_string()));
|
|
self.set_pseudo_method_comp(&uri, pos, &comp_kind, &mut item)?;
|
|
already_appeared.insert(item.label.clone());
|
|
result.push(item);
|
|
}
|
|
if comp_kind.should_be_local() {
|
|
if let Some(comps) = self.comp_cache.get("<module>") {
|
|
result.extend(comps.clone());
|
|
} else {
|
|
let comps = module_completions();
|
|
self.comp_cache.insert("<module>".into(), comps.clone());
|
|
result.extend(comps);
|
|
}
|
|
if !self
|
|
.disabled_features
|
|
.contains(&DefaultFeatures::DeepCompletion)
|
|
{
|
|
result.extend(self.neighbor_completion(&uri, arg_pt, &mut already_appeared));
|
|
}
|
|
}
|
|
_log!(self, "completion items: {}", result.len());
|
|
Ok(Some(CompletionResponse::Array(result)))
|
|
}
|
|
|
|
// s.`Function::map` => map(s)
|
|
fn set_pseudo_method_comp(
|
|
&self,
|
|
uri: &NormalizedUrl,
|
|
pos: Position,
|
|
comp_kind: &CompletionKind,
|
|
item: &mut CompletionItem,
|
|
) -> ELSResult<()> {
|
|
if comp_kind.should_be_method() && item.label.starts_with("Function::") {
|
|
let receiver = self.get_receiver(uri, pos)?;
|
|
if let Some(mut range) = receiver.as_ref().and_then(|expr| loc_to_range(expr.loc())) {
|
|
// FIXME:
|
|
let s_receiver = self.file_cache.get_ranged(uri, range)?.unwrap_or_default();
|
|
range.end.character += 1;
|
|
let name = item.label.trim_start_matches("Function::");
|
|
let remove = TextEdit::new(range, "".to_string());
|
|
item.insert_text = Some(format!("{name}({s_receiver})"));
|
|
item.additional_text_edits = Some(vec![remove]);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn get_attr_completion_by_name(
|
|
&self,
|
|
comp_kind: &CompletionKind,
|
|
attr: &str,
|
|
ctx: &Context,
|
|
) -> ELSResult<Vec<CompletionItem>> {
|
|
let mut items = vec![];
|
|
if comp_kind.should_be_method() {
|
|
let attr = Identifier::public(erg_common::Str::rc(attr));
|
|
for (name, methods) in ctx.partial_get_methods_by_name(&attr) {
|
|
for method in methods {
|
|
let detail =
|
|
format!("{} (of {})", method.method_info.t, method.definition_type);
|
|
let mut item = CompletionItem::new_simple(name.to_string(), detail);
|
|
item.kind = Some(comp_item_kind(&method.method_info.t, Mutability::Immutable));
|
|
items.push(item);
|
|
}
|
|
}
|
|
}
|
|
Ok(items)
|
|
}
|
|
|
|
fn magic_completion_items(
|
|
&self,
|
|
comp_kind: &CompletionKind,
|
|
receiver_t: &Type,
|
|
uri: &NormalizedUrl,
|
|
pos: Position,
|
|
ctx: &Context,
|
|
) -> ELSResult<Vec<CompletionItem>> {
|
|
let mut items = vec![];
|
|
// magic completion
|
|
// `expr.if` => `if expr, do:`
|
|
// `expr.for!` => `for! expr, i =>`
|
|
if comp_kind.should_be_method() {
|
|
let Some(receiver) = self.get_receiver(uri, pos)? else {
|
|
return Ok(items);
|
|
};
|
|
let mut range = loc_to_range(receiver.loc()).unwrap();
|
|
let s_receiver = self.file_cache.get_ranged(uri, range)?.unwrap_or_default();
|
|
// receiver + `.`
|
|
range.end.character += 1;
|
|
let remove = TextEdit::new(range, "".into());
|
|
if ctx.subtype_of(receiver_t, &Type::Bool) {
|
|
let mut item_if =
|
|
CompletionItem::new_simple("if".into(), "magic completion".into());
|
|
let code = if PYTHON_MODE {
|
|
format!("if {s_receiver}:")
|
|
} else {
|
|
format!("if {s_receiver}, do:")
|
|
};
|
|
item_if.insert_text = Some(code);
|
|
item_if.additional_text_edits = Some(vec![remove.clone()]);
|
|
items.push(item_if);
|
|
if ERG_MODE {
|
|
let mut item_if =
|
|
CompletionItem::new_simple("if!".into(), "magic completion".into());
|
|
item_if.insert_text = Some(format!("if! {s_receiver}, do!:"));
|
|
item_if.additional_text_edits = Some(vec![remove.clone()]);
|
|
items.push(item_if);
|
|
}
|
|
let mut item_while =
|
|
CompletionItem::new_simple("while!".into(), "magic completion".into());
|
|
let code = if PYTHON_MODE {
|
|
format!("while {s_receiver}:")
|
|
} else {
|
|
format!("while! do! {s_receiver}, do!:")
|
|
};
|
|
item_while.insert_text = Some(code);
|
|
item_while.additional_text_edits = Some(vec![remove]);
|
|
items.push(item_while);
|
|
} else if ctx.subtype_of(receiver_t, &poly("Iterable", vec![ty_tp(Type::Obj)])) {
|
|
let mut item_for =
|
|
CompletionItem::new_simple("for!".into(), "magic completion".into());
|
|
let code = if PYTHON_MODE {
|
|
format!("for i in {s_receiver}:")
|
|
} else {
|
|
format!("for! {s_receiver}, i =>")
|
|
};
|
|
item_for.insert_text = Some(code);
|
|
item_for.additional_text_edits = Some(vec![remove]);
|
|
items.push(item_for);
|
|
}
|
|
}
|
|
Ok(items)
|
|
}
|
|
|
|
pub(crate) fn handle_resolve_completion(
|
|
&mut self,
|
|
mut item: CompletionItem,
|
|
) -> ELSResult<CompletionItem> {
|
|
self.send_log(format!("completion resolve requested: {item:?}"))?;
|
|
if let Some(data) = &item.data {
|
|
let mut contents = vec![];
|
|
let Ok(def_loc) = data.as_str().unwrap_or_default().parse::<AbsLocation>() else {
|
|
return Ok(item);
|
|
};
|
|
self.show_doc_comment(None, &mut contents, &def_loc)?;
|
|
let mut contents = contents.into_iter().map(mark_to_string).collect::<Vec<_>>();
|
|
contents.sort_by_key(|cont| markdown_order(cont));
|
|
item.documentation = Some(Documentation::MarkupContent(MarkupContent {
|
|
kind: MarkupKind::Markdown,
|
|
value: contents.join("\n"),
|
|
}));
|
|
}
|
|
Ok(item)
|
|
}
|
|
}
|