diff --git a/server/src/core/import_resolver.rs b/server/src/core/import_resolver.rs index 7b08d0f7..f4495e89 100644 --- a/server/src/core/import_resolver.rs +++ b/server/src/core/import_resolver.rs @@ -1,6 +1,8 @@ use glob::glob; -use lsp_types::{Diagnostic, DiagnosticTag, Position, Range}; +use itertools::Itertools; +use lsp_types::{CompletionItemKind, Diagnostic, DiagnosticTag, Position, Range}; use ruff_python_ast::name::Name; +use serde::Serialize; use tracing::error; use std::collections::{HashMap, HashSet}; use std::rc::Rc; @@ -19,10 +21,11 @@ use super::odoo::SyncOdoo; use super::symbols::symbol::Symbol; pub struct ImportResult { - pub name: OYarn, + pub name: OYarn, //the last imported element + pub var_name: OYarn, // the effective symbol name (asname, or first part in a import A.B.C) pub found: bool, pub symbol: Rc>, - pub file_tree: Tree, + pub file_tree: Vec, //contains only the first part of a Tree pub range: TextRange, } @@ -34,7 +37,7 @@ pub struct ImportCache { pub main_modules: HashMap>>>, } -fn resolve_import_stmt_hook(alias: &Alias, from_symbol: &Option>>, session: &mut SessionInfo, source_file_symbol: &Rc>, from_stmt: Option<&Identifier>, level: Option, diagnostics: &mut Option<&mut Vec>) -> Option{ +fn resolve_import_stmt_hook(alias: &Alias, from_symbol: &Option>>, session: &mut SessionInfo, source_file_symbol: &Rc>, from_stmt: Option<&Identifier>, level: u32, diagnostics: &mut Option<&mut Vec>) -> Option{ if session.sync_odoo.version_major >= 17 && alias.name.as_str() == "Form" && (*(from_symbol.as_ref().unwrap())).borrow().get_main_entry_tree(session).0 == vec!["odoo", "tests", "common"]{ let mut results = resolve_import_stmt(session, source_file_symbol, Some(&Identifier::new(S!("odoo.tests"), from_stmt.unwrap().range)), &[alias.clone()], level, &mut None); if let Some(diagnostic) = diagnostics.as_mut() { @@ -55,7 +58,7 @@ fn resolve_import_stmt_hook(alias: &Alias, from_symbol: &Option>, from_stmt:Option, name: &str, asname: Option, level: Option, diagnostics: &mut Option<&mut Vec>) -> Vec { +pub fn manual_import(session: &mut SessionInfo, source_file_symbol: &Rc>, from_stmt:Option, name: &str, asname: Option, level: u32, diagnostics: &mut Option<&mut Vec>) -> Vec { let name_aliases = vec![Alias { name: Identifier { id: Name::new(name), range: TextRange::new(TextSize::new(0), TextSize::new(0)), node_index: AtomicNodeIndex::dummy() }, asname: match asname { @@ -72,8 +75,7 @@ pub fn manual_import(session: &mut SessionInfo, source_file_symbol: &Rc>, from_stmt: Option<&Identifier>, name_aliases: &[Alias], level: Option, diagnostics: &mut Option<&mut Vec>) -> Vec { - //A: search base of different imports +pub fn resolve_from_stmt(session: &mut SessionInfo, source_file_symbol: &Rc>, from_stmt: Option<&Identifier>, level: u32) -> (Option>>, Option>>, Vec) { let source_root = source_file_symbol.borrow().get_root().as_ref().unwrap().upgrade().unwrap(); let entry = source_root.borrow().get_entry().unwrap(); let _source_file_symbol_lock = source_file_symbol.borrow_mut(); @@ -84,29 +86,38 @@ pub fn resolve_import_stmt(session: &mut SessionInfo, source_file_symbol: &Rc>, from_stmt: Option<&Identifier>, name_aliases: &[Alias], level: u32, diagnostics: &mut Option<&mut Vec>) -> Vec { + //A: search base of different imports + let source_root = source_file_symbol.borrow().get_root().as_ref().unwrap().upgrade().unwrap(); + let entry = source_root.borrow().get_entry().unwrap(); + let (from_symbol, fallback_sym, file_tree) = resolve_from_stmt(session, source_file_symbol, from_stmt, level); let mut result = vec![]; for alias in name_aliases { result.push(ImportResult{ - name: OYarn::from(alias.asname.as_ref().unwrap_or(&alias.name).to_string()), + name: OYarn::from(alias.name.as_ref().to_string()), + var_name: OYarn::from(alias.asname.as_ref().unwrap_or(&alias.name).to_string()), found: false, - symbol: fallback_sym.as_ref().unwrap_or(&source_root).clone(), - file_tree: (file_tree.clone(), vec![]), + symbol: fallback_sym.as_ref().unwrap().clone(), + file_tree: file_tree.clone(), range: alias.range.clone() }) } - if from_symbol.is_none() && level.is_some() { + if from_symbol.is_none() && from_stmt.is_some() { return result; } @@ -130,6 +141,7 @@ pub fn resolve_import_stmt(session: &mut SessionInfo, source_file_symbol: &Rc = vec![name_split.last().unwrap().clone()]; let (mut next_symbol, mut fallback_sym) = _get_or_create_symbol( session, @@ -138,7 +150,7 @@ pub fn resolve_import_stmt(session: &mut SessionInfo, source_file_symbol: &Rc>, None } -fn _resolve_packages(from_file: &Symbol, level: Option, from_stmt: Option<&Identifier>) -> Vec { +fn _resolve_packages(from_file: &Symbol, level: u32, from_stmt: Option<&Identifier>) -> Vec { let mut first_part_tree: Vec = vec![]; - if level.is_some() && level.unwrap() > 0 { - let mut lvl = level.unwrap(); + if level > 0 { + let mut lvl = level; if lvl > Path::new(&from_file.paths()[0]).components().count() as u32 { panic!("Level is too high!") } @@ -259,10 +275,13 @@ fn _resolve_packages(from_file: &Symbol, level: Option, from_stmt: Option<& first_part_tree } -fn _get_or_create_symbol(session: &mut SessionInfo, for_entry: &Rc>, from_path: &str, symbol: Option>>, names: &Vec, asname: Option, level: Option) -> (Option>>, Option>>) { +fn _get_or_create_symbol(session: &mut SessionInfo, for_entry: &Rc>, from_path: &str, symbol: Option>>, names: &Vec, asname: Option, level: u32) -> (Option>>, Option>>) { let mut sym: Option>> = symbol.clone(); let mut last_symbol = symbol.clone(); for branch in names.iter() { + if branch.is_empty() { + continue; + } match sym { Some(ref s) => { let mut next_symbol = s.borrow().get_symbol(&(vec![branch.clone()], vec![]), u32::MAX); @@ -280,7 +299,7 @@ fn _get_or_create_symbol(session: &mut SessionInfo, for_entry: &Rc { - if level.is_none() || level.unwrap() == 0 { + if level == 0 { if let Some(ref cache) = session.sync_odoo.import_cache { let cache_module = if for_entry.borrow().typ == EntryPointType::MAIN || for_entry.borrow().typ == EntryPointType::ADDON { cache.main_modules.get(branch) @@ -307,7 +326,7 @@ fn _get_or_create_symbol(session: &mut SessionInfo, for_entry: &Rc>, name: &OYarn, asname: Option) -> Result>, String> { + if name == "" { + return Err("Empty name".to_string()); + } if DEBUG_BORROW_GUARDS { //Parent must be borrowable in this function parent.borrow_mut(); @@ -434,64 +456,172 @@ fn _resolve_new_symbol(session: &mut SessionInfo, parent: Rc>, n return Err("Symbol not found".to_string()) } -pub fn get_all_valid_names(session: &mut SessionInfo, source_file_symbol: &Rc>, from_stmt: Option<&Identifier>, base_name: String, level: Option) -> HashSet { - //A: search base of different imports +/* +Used for autocompletion. Given a base_name, return all valid names that can be used to complete it. +is_from indicates if the import is the X in "from X import Y". Else it is Y from "import Y" or "from X import Y" +*/ +pub fn get_all_valid_names(session: &mut SessionInfo, source_file_symbol: &Rc>, from_stmt: Option, import: String, level: u32, is_from: bool) -> HashMap { + let (identifier_from, to_complete) = match from_stmt { + Some(from_stmt_inner) => { + if is_from { + let split = from_stmt_inner.split(".").collect::>(); + if split.len() > 1 { + (Some(Identifier::new(split[0..split.len()-1].join(".").as_str(), TextRange::default())), split.last().unwrap().to_string()) + } else { + (None, split.last().unwrap().to_string()) + } + } else { + (Some(Identifier::new(from_stmt_inner.clone(), TextRange::default())), import.clone()) + } + }, + None => (None, import.split(".").last().unwrap().to_string()), + }; let source_root = source_file_symbol.borrow().get_root().as_ref().unwrap().upgrade().unwrap(); let entry = source_root.borrow().get_entry().unwrap(); - let _source_file_symbol_lock = source_file_symbol.borrow_mut(); - let file_tree = _resolve_packages( - &_source_file_symbol_lock, - level, - from_stmt); - drop(_source_file_symbol_lock); - let mut start_symbol = None; - if level.is_some() { - //if level is some, resolve_pacackages already built a full tree, so we can start from root - start_symbol = Some(source_root.clone()); - } + let (mut from_symbol, _fallback_sym, file_tree) = resolve_from_stmt(session, source_file_symbol, identifier_from.as_ref(), level); let source_path = source_file_symbol.borrow().paths()[0].clone(); - let (from_symbol, _fallback_sym) = _get_or_create_symbol(session, - &entry, - source_path.as_str(), - start_symbol, - &file_tree, - None, - level); - let mut result = HashSet::new(); + let mut result = HashMap::new(); + let mut symbols_to_browse = vec![]; if from_symbol.is_none() { + if !file_tree.is_empty() { //symbol was not found + return result; + } else { //nothing was provided, so we have to add the root symbol of any valid entrypoint + let entry_point_mgr = session.sync_odoo.entry_point_mgr.clone(); + let entry_point_mgr = entry_point_mgr.borrow(); + let from_path = session.sync_odoo.entry_point_mgr.borrow().transform_addon_path(&PathBuf::from(source_path.clone())); + let from_path = PathBuf::from(from_path); + for entry in entry_point_mgr.iter_for_import(&entry) { + if (entry.borrow().is_public() && (level == 0)) || entry.borrow().is_valid_for(&from_path) { + let entry_point = entry.borrow().get_symbol(); + if let Some(entry_point) = entry_point { + symbols_to_browse.push(entry_point.clone()); + } + } + } + if symbols_to_browse.is_empty() { + return result; + } + } + } + if is_from { + if let Some(fs) = from_symbol { + symbols_to_browse.push(fs); + } + for symbol_to_browse in symbols_to_browse.iter() { + let valid_names = valid_names_for_a_symbol(session, symbol_to_browse, &oyarn!("{}", to_complete), true); + result.extend(valid_names); + } return result; } - let from_symbol = from_symbol.unwrap(); - let mut sym: Option>> = Some(from_symbol.clone()); - let mut names = vec![base_name.split(".").map(|s| oyarn!("{}", s)).next().unwrap()]; - if base_name.ends_with(".") { - names.push(Sy!("")); + let import_parts = import.split(".").collect::>(); + if import_parts.len() > 1 { + let (next_symbol, _fallback_sym) = _get_or_create_symbol( + session, + &entry, + source_path.as_str(), + from_symbol.clone(), + &import_parts[0..import_parts.len()-1].iter().map(|s| oyarn!("{}", *s)).collect(), + None, + level, + ); + if next_symbol.is_none() { + return result; + } + from_symbol = next_symbol.clone(); } - for (index, branch) in names.iter().enumerate() { - if index != names.len() -1 { - let mut next_symbol = sym.as_ref().unwrap().borrow().get_symbol(&(vec![branch.clone()], vec![]), u32::MAX); - if next_symbol.is_empty() { - next_symbol = match _resolve_new_symbol(session, sym.as_ref().unwrap().clone(), &branch, None) { - Ok(v) => vec![v], - Err(_) => vec![] - } + if let Some(fs) = from_symbol { + symbols_to_browse.clear(); + symbols_to_browse.push(fs); + } + for symbol_to_browse in symbols_to_browse.iter() { + let valid_names = valid_names_for_a_symbol(session, symbol_to_browse, &oyarn!("{}", to_complete), false); + result.extend(valid_names); + } + result +} + +fn valid_names_for_a_symbol(session: &mut SessionInfo, symbol: &Rc>, start_filter: &OYarn, only_on_disk: bool) -> HashMap { + let mut res = HashMap::new(); + match symbol.borrow().typ() { + SymType::FILE => { + if only_on_disk { + return res; } - if next_symbol.is_empty() { - sym = None; - break; + res.extend(valid_name_from_symbol(symbol, start_filter)); + }, + SymType::NAMESPACE | SymType::DISK_DIR => { + for path in symbol.borrow().paths().iter() { + res.extend(valid_name_from_disk(path, start_filter)); } - sym = Some(next_symbol[0].clone()); + }, + SymType::PACKAGE(_) => { + for path in symbol.borrow().paths().iter() { + res.extend(valid_name_from_disk(path, start_filter)); + } + if only_on_disk { + return res; + } + res.extend(valid_name_from_symbol(symbol, start_filter)); + } + SymType::CLASS | SymType::COMPILED | SymType::CSV_FILE | SymType::XML_FILE | SymType::FUNCTION | SymType::ROOT | SymType::VARIABLE => { } } - if let Some(sym) = sym { - let filter = names.last().unwrap(); - for symbol in sym.borrow().all_symbols() { - if symbol.borrow().name().starts_with(filter.as_str()) { - result.insert(symbol.borrow().name().clone()); + res +} + +fn valid_name_from_disk(path: &String, start_filter: &OYarn) -> HashMap { + let mut res = HashMap::new(); + if is_dir_cs(path.clone()) { + if let Ok(entries) = std::fs::read_dir(path) { + for entry in entries { + if let Ok(entry) = entry { + let Ok(file_type) = entry.file_type() else { + continue; + }; + if file_type.is_dir() { + let dir_name = entry.file_name(); + let dir_name_str = dir_name.to_string_lossy(); + if dir_name_str.starts_with(start_filter.as_str()) { + let mut typ = SymType::NAMESPACE; + if Path::new(&path).join(dir_name_str.to_string()).join("__init__.py").exists() { + typ = SymType::PACKAGE(PackageType::PYTHON_PACKAGE); + } + res.insert(Sy!(dir_name_str.to_string()), typ); + } + } else if file_type.is_file() { + let file_name = entry.file_name(); + let file_name_str = file_name.to_string_lossy().to_string(); + if (file_name_str.ends_with(".py") || file_name_str.ends_with(".pyi")) && file_name_str.starts_with(start_filter.as_str()) { + let Some(stem) = Path::new(&file_name_str).file_stem() else {continue}; + let Some(filename) = stem.to_str() else {continue}; + if filename == "__init__" {continue;} + res.insert(Sy!(filename.to_string()), SymType::FILE); + } + } + //TODO support for symlinks? + } } } } + res +} - return result; +fn valid_name_from_symbol(symbol: &Rc>, start_filter: &OYarn) -> HashMap { + let mut res = HashMap::new(); + let symbols = symbol.borrow(); + for s in symbols.iter_symbols() { + if s.0.starts_with(&start_filter.to_string()) { + let mut typ = SymType::VARIABLE; + let a_section = s.1.iter().last(); //let's take the last section, anyway we can display only one icon + if let Some(a_section) = a_section { + let last = a_section.1.last(); + if let Some(last) = last { + typ = last.borrow().typ(); + } + } + res.insert(s.0.clone(), typ); + } + } + res } diff --git a/server/src/core/python_arch_builder.rs b/server/src/core/python_arch_builder.rs index 75e1f8d9..1f4e3f55 100644 --- a/server/src/core/python_arch_builder.rs +++ b/server/src/core/python_arch_builder.rs @@ -155,7 +155,7 @@ impl PythonArchBuilder { symbol.set_build_status(BuildSteps::ARCH, BuildStatus::DONE); } - fn create_local_symbols_from_import_stmt(&mut self, session: &mut SessionInfo, from_stmt: Option<&Identifier>, name_aliases: &[Alias], level: Option, _range: &TextRange) -> Result<(), Error> { + fn create_local_symbols_from_import_stmt(&mut self, session: &mut SessionInfo, from_stmt: Option<&Identifier>, name_aliases: &[Alias], level: u32, _range: &TextRange) -> Result<(), Error> { for import_name in name_aliases { if import_name.name.as_str() == "*" { if self.sym_stack.len() != 1 { //only at top level for now. @@ -170,8 +170,7 @@ impl PythonArchBuilder { &mut None).remove(0); //we don't need the vector with this call as there will be 1 result. if !import_result.found { self.entry_point.borrow_mut().not_found_symbols.insert(self.file.clone()); - let file_tree_flattened = [import_result.file_tree.0.clone(), import_result.file_tree.1.clone()].concat(); - self.file.borrow_mut().not_found_paths_mut().push((self.current_step, file_tree_flattened)); + self.file.borrow_mut().not_found_paths_mut().push((self.current_step, import_result.file_tree.clone())); continue; } let mut all_name_allowed = true; @@ -255,10 +254,10 @@ impl PythonArchBuilder { for stmt in nodes.iter() { match stmt { Stmt::Import(import_stmt) => { - self.create_local_symbols_from_import_stmt(session, None, &import_stmt.names, None, &import_stmt.range)? + self.create_local_symbols_from_import_stmt(session, None, &import_stmt.names, 0, &import_stmt.range)? }, Stmt::ImportFrom(import_from_stmt) => { - self.create_local_symbols_from_import_stmt(session, import_from_stmt.module.as_ref(), &import_from_stmt.names, Some(import_from_stmt.level), &import_from_stmt.range)? + self.create_local_symbols_from_import_stmt(session, import_from_stmt.module.as_ref(), &import_from_stmt.names, import_from_stmt.level, &import_from_stmt.range)? }, Stmt::AnnAssign(ann_assign_stmt) => { self._visit_ann_assign(session, ann_assign_stmt); diff --git a/server/src/core/python_arch_builder_hooks.rs b/server/src/core/python_arch_builder_hooks.rs index ee1878c8..77a95304 100644 --- a/server/src/core/python_arch_builder_hooks.rs +++ b/server/src/core/python_arch_builder_hooks.rs @@ -230,7 +230,7 @@ impl PythonArchBuilderHooks { main_odoo_symbol = main_ep.borrow().get_symbol(); } if let Some(main_odoo_symbol) = main_odoo_symbol { - let werkzeug_patch = manual_import(session, &main_odoo_symbol, Some(full_path_monkeypatches), "werkzeug", None, None, &mut None); + let werkzeug_patch = manual_import(session, &main_odoo_symbol, Some(full_path_monkeypatches), "werkzeug", None, 0, &mut None); for werkzeug_patch in werkzeug_patch { if werkzeug_patch.found { info!("monkeypatch manually found"); diff --git a/server/src/core/python_arch_eval.rs b/server/src/core/python_arch_eval.rs index abfeadbb..408fac77 100644 --- a/server/src/core/python_arch_eval.rs +++ b/server/src/core/python_arch_eval.rs @@ -146,10 +146,10 @@ impl PythonArchEval { fn visit_stmt(&mut self, session: &mut SessionInfo, stmt: &Stmt) { match stmt { Stmt::Import(import_stmt) => { - self.eval_symbols_from_import_stmt(session, None, &import_stmt.names, None, &import_stmt.range) + self.eval_symbols_from_import_stmt(session, None, &import_stmt.names, 0, &import_stmt.range) }, Stmt::ImportFrom(import_from_stmt) => { - self.eval_symbols_from_import_stmt(session, import_from_stmt.module.as_ref(), &import_from_stmt.names, Some(import_from_stmt.level), &import_from_stmt.range) + self.eval_symbols_from_import_stmt(session, import_from_stmt.module.as_ref(), &import_from_stmt.names, import_from_stmt.level, &import_from_stmt.range) }, Stmt::ClassDef(class_stmt) => { self.visit_class_def(session, class_stmt); @@ -379,7 +379,7 @@ impl PythonArchEval { false } - fn eval_symbols_from_import_stmt(&mut self, session: &mut SessionInfo, from_stmt: Option<&Identifier>, name_aliases: &[Alias], level: Option, _range: &TextRange) { + fn eval_symbols_from_import_stmt(&mut self, session: &mut SessionInfo, from_stmt: Option<&Identifier>, name_aliases: &[Alias], level: u32, _range: &TextRange) { if name_aliases.len() == 1 && name_aliases[0].name.to_string() == "*" { return; } @@ -392,7 +392,7 @@ impl PythonArchEval { &mut Some(&mut self.diagnostics)); for _import_result in import_results.iter() { - let variable = self.sym_stack.last().unwrap().borrow().get_positioned_symbol(&_import_result.name, &_import_result.range); + let variable = self.sym_stack.last().unwrap().borrow().get_positioned_symbol(&_import_result.var_name, &_import_result.range); let Some(variable) = variable.clone() else { continue; }; @@ -413,7 +413,7 @@ impl PythonArchEval { } } } else { - let mut file_tree = [_import_result.file_tree.0.clone(), _import_result.file_tree.1.clone()].concat(); + let mut file_tree = _import_result.file_tree.clone(); file_tree.extend(_import_result.name.split(".").map(|s| oyarn!("{}", s))); self.file.borrow_mut().not_found_paths_mut().push((self.current_step, file_tree.clone())); self.entry_point.borrow_mut().not_found_symbols.insert(self.file.clone()); @@ -428,9 +428,9 @@ impl PythonArchEval { } } else { - let mut file_tree = [_import_result.file_tree.0.clone(), _import_result.file_tree.1.clone()].concat(); + let mut file_tree = _import_result.file_tree.clone(); file_tree.extend(_import_result.name.split(".").map(|s| oyarn!("{}", s))); - if BUILT_IN_LIBS.contains(&file_tree[0].as_str()) { + if session.sync_odoo.config.diag_missing_imports != DiagMissingImportsMode::All && BUILT_IN_LIBS.contains(&file_tree[0].as_str()) { continue; } if !self.safe_import.last().unwrap() { diff --git a/server/src/features/ast_utils.rs b/server/src/features/ast_utils.rs index e85f04eb..dc5e744e 100644 --- a/server/src/features/ast_utils.rs +++ b/server/src/features/ast_utils.rs @@ -4,13 +4,15 @@ use std::cell::RefCell; use crate::constants::{BuildStatus, BuildSteps, SymType}; use crate::core::evaluation::{AnalyzeAstResult, Context, ContextValue, Evaluation, ExprOrIdent}; use crate::core::odoo::SyncOdoo; +use crate::core::import_resolver::{resolve_from_stmt, resolve_import_stmt}; use crate::core::symbols::symbol::Symbol; use crate::core::file_mgr::FileInfo; use crate::threads::SessionInfo; use crate::S; +use ruff_python_ast::name::Name; use ruff_python_ast::visitor::{Visitor, walk_expr, walk_stmt, walk_alias, walk_except_handler, walk_parameter, walk_keyword, walk_pattern_keyword, walk_type_param, walk_pattern}; -use ruff_python_ast::{Alias, ExceptHandler, Expr, ExprCall, Keyword, Parameter, Pattern, PatternKeyword, Stmt, TypeParam}; -use ruff_text_size::{Ranged, TextRange, TextSize}; +use ruff_python_ast::{Alias, AtomicNodeIndex, ExceptHandler, Expr, ExprCall, Identifier, Keyword, Parameter, Pattern, PatternKeyword, Stmt, TypeParam}; +use ruff_text_size::{Ranged, TextRange, TextSize, TextSlice}; use tracing::warn; pub struct AstUtils {} @@ -23,6 +25,10 @@ impl AstUtils { let file_info_ast = file_info.borrow().file_info_ast.clone(); let file_info_ast = file_info_ast.borrow(); for stmt in file_info_ast.get_stmts().unwrap().iter() { + //we have to handle imports differently as symbols are not visible in file. + if let Some(test_import) = Self::get_symbol_in_import(session, file_symbol, offset, stmt) { + return test_import; + } (expr, call_expr) = ExprFinderVisitor::find_expr_at(stmt, offset); if expr.is_some() { break; @@ -75,6 +81,99 @@ impl AstUtils { } } + fn get_symbol_in_import(session: &mut SessionInfo, file_symbol: &Rc>, offset: u32, stmt: &Stmt) -> Option<(AnalyzeAstResult, Option, Option)> { + match stmt { + //for all imports, the idea will be to check if we are on the last name of the import (then it has benn imported already and we can fallback on it), + //or then take the full tree to the offset symbol and resolve_import on it as it was in a 'from' clause. + Stmt::Import(stmt) => { + for alias in stmt.names.iter() { + if alias.range().contains(TextSize::new(offset)) { + let mut is_last = false; + let (to_analyze, range) = if alias.name.range().contains(TextSize::new(offset)) { + let next_dot_offset = alias.name.id.as_str()[offset as usize - alias.range().start().to_usize()..].find("."); + if let Some(next_dot_offset) = next_dot_offset { + let end = offset as usize + next_dot_offset; + let text = &alias.name.id.as_str()[..end - alias.range().start().to_usize()]; + let start_range = text.rfind(".").map(|p| p+1).unwrap_or(0) + alias.name.range().start().to_usize(); + (text, TextRange::new(TextSize::new(start_range as u32), TextSize::new(end as u32))) + } else { + is_last = true; + (alias.name.id.as_str(), alias.name.range()) + } + } else if alias.asname.is_some() && alias.asname.as_ref().unwrap().range().contains(TextSize::new(offset)) { + is_last = true; + (alias.asname.as_ref().unwrap().id.as_str(), alias.asname.as_ref().unwrap().range()) + } else { + return None; + }; + if !is_last { + //we import as a from_stmt, to refuse import of variables, as the import stmt is not complete + let to_analyze = Identifier { id: Name::new(to_analyze), range: TextRange::new(TextSize::new(0), TextSize::new(0)), node_index: AtomicNodeIndex::dummy() }; + let (from_symbol, _fallback_sym, _file_tree) = resolve_from_stmt(session, file_symbol, Some(&to_analyze), 0); + if let Some(symbol) = from_symbol { + let result = AnalyzeAstResult { + evaluations: vec![Evaluation::eval_from_symbol(&Rc::downgrade(&symbol), None)], + diagnostics: vec![], + }; + return Some((result, Some(range), None)); + } + } else { + let res = resolve_import_stmt(session, file_symbol, None, &[ + Alias { //create a dummy alias with a asname to force full import + name: Identifier { id: Name::new(to_analyze), range: TextRange::new(TextSize::new(0), TextSize::new(0)), node_index: AtomicNodeIndex::dummy() }, + asname: Some(Identifier { id: Name::new("fake_name"), range: alias.name.range().clone(), node_index: AtomicNodeIndex::dummy() }), + range: alias.range(), + node_index: AtomicNodeIndex::dummy() + }], 0, &mut None); + let res = res.into_iter().filter(|s| s.found).collect::>(); + if !res.is_empty() { + let result = AnalyzeAstResult { + evaluations: res.iter().map( + |s| Evaluation::eval_from_symbol(&Rc::downgrade(&s.symbol), None) + ).collect(), + diagnostics: vec![], + }; + return Some((result, Some(range), None)); + } + } + return None; + } + } + }, + Stmt::ImportFrom(stmt) => { + //only check module as names are already supported by default ast walking and name resolution + if stmt.module.is_some() && stmt.module.as_ref().unwrap().range().contains(TextSize::new(offset)) { + let module = stmt.module.as_ref().unwrap(); + let (to_analyze, range) = if module.range().contains(TextSize::new(offset)) { + let next_dot_offset = module.id.as_str()[offset as usize - module.range().start().to_usize()..].find("."); + if let Some(next_dot_offset) = next_dot_offset { + let end = offset as usize + next_dot_offset; + let text = &module.id.as_str()[..end - module.range().start().to_usize()]; + let start_range = text.rfind(".").map(|p| p+1).unwrap_or(0) + module.range().start().to_usize(); + (text, TextRange::new(TextSize::new(start_range as u32), TextSize::new(end as u32))) + } else { + (module.id.as_str(), module.range()) + } + } else { + return None; + }; + let to_analyze = Identifier { id: Name::new(to_analyze), range: TextRange::new(TextSize::new(0), TextSize::new(0)), node_index: AtomicNodeIndex::dummy() }; + let (from_symbol, _fallback_sym, _file_tree) = resolve_from_stmt(session, file_symbol, Some(&to_analyze), 0); + if let Some(symbol) = from_symbol { + let result = AnalyzeAstResult { + evaluations: vec![Evaluation::eval_from_symbol(&Rc::downgrade(&symbol), None)], + diagnostics: vec![], + }; + return Some((result, Some(range), None)); + } + } + }, + _ => { + return None; + } + } + None + } } @@ -225,7 +324,6 @@ impl<'a> Visitor<'a> for ExprFinderVisitor<'a> { let idents = match stmt { Stmt::FunctionDef(stmt) => vec![&stmt.name], Stmt::ClassDef(stmt) => vec![&stmt.name], - Stmt::ImportFrom(stmt) => if let Some(ref module) = stmt.module {vec![module]} else {vec![]}, Stmt::Global(stmt) => stmt.names.iter().collect(), Stmt::Nonlocal(stmt) => stmt.names.iter().collect(), _ => vec![], @@ -237,8 +335,6 @@ impl<'a> Visitor<'a> for ExprFinderVisitor<'a> { break; } } - } else { - walk_stmt(self, stmt); } } } diff --git a/server/src/features/completion.rs b/server/src/features/completion.rs index 1ed3d11c..debb2d43 100644 --- a/server/src/features/completion.rs +++ b/server/src/features/completion.rs @@ -309,12 +309,13 @@ fn complete_assert_stmt(session: &mut SessionInfo<'_>, file: &Rc fn complete_import_stmt(session: &mut SessionInfo, file: &Rc>, stmt_import: &StmtImport, offset: usize) -> Option { let mut items = vec![]; for alias in stmt_import.names.iter() { - if alias.name.range().end().to_usize() == offset { - let names = import_resolver::get_all_valid_names(session, file, None, S!(alias.name.id.as_str()), None); - for name in names { + if alias.name.range().start().to_usize() < offset && alias.name.range.end().to_usize() >= offset { + let to_complete = alias.name.id.to_string().get(0 .. offset - alias.name.range.start().to_usize()).unwrap_or("").to_string(); + let names = import_resolver::get_all_valid_names(session, file, None, to_complete, 0, false); + for (name, sym_typ) in names { items.push(CompletionItem { label: name.to_string(), - kind: Some(lsp_types::CompletionItemKind::MODULE), + kind: Some(get_completion_item_kind(&sym_typ)), ..Default::default() }); } @@ -329,24 +330,26 @@ fn complete_import_stmt(session: &mut SessionInfo, file: &Rc>, s fn complete_import_from_stmt(session: &mut SessionInfo, file: &Rc>, stmt_import: &StmtImportFrom, offset: usize) -> Option { let mut items = vec![]; if let Some(module) = stmt_import.module.as_ref() { - if module.range.end().to_usize() == offset && !stmt_import.names.is_empty() { - let names = import_resolver::get_all_valid_names(session, file, None, S!(stmt_import.names[0].name.id.as_str()), Some(stmt_import.level)); - for name in names { + if module.range.start().to_usize() < offset && module.range.end().to_usize() >= offset { + let to_complete = module.id.to_string().get(0 .. offset - module.range.start().to_usize()).unwrap_or("").to_string(); + let names = import_resolver::get_all_valid_names(session, file, Some(to_complete), S!(""), stmt_import.level, true); + for (name, sym_type) in names { items.push(CompletionItem { label: name.to_string(), - kind: Some(lsp_types::CompletionItemKind::MODULE), + kind: Some(get_completion_item_kind(&sym_type)), ..Default::default() }); } } } for alias in stmt_import.names.iter() { - if alias.name.range().end().to_usize() == offset { - let names = import_resolver::get_all_valid_names(session, file, stmt_import.module.as_ref(), S!(alias.name.id.as_str()), Some(stmt_import.level)); - for name in names { + if alias.name.range().start().to_usize() < offset && alias.name.range.end().to_usize() >= offset { + let to_complete = alias.name.id.to_string().get(0 .. offset - alias.name.range.start().to_usize()).unwrap_or("").to_string(); + let names = import_resolver::get_all_valid_names(session, file, stmt_import.module.as_ref().map(|m| m.id.to_string()), to_complete, stmt_import.level, false); + for (name, sym_type) in names { items.push(CompletionItem { label: name.to_string(), - kind: Some(lsp_types::CompletionItemKind::MODULE), + kind: Some(get_completion_item_kind(&sym_type)), ..Default::default() }); } @@ -1091,7 +1094,7 @@ fn build_completion_item_from_symbol(session: &mut SessionInfo, symbols: Vec>/*, cl: Option>) -> CompletionItemKind { - match symbol.borrow().typ() { +fn get_completion_item_kind(typ: &SymType) -> CompletionItemKind { + match typ { SymType::ROOT => CompletionItemKind::TEXT, SymType::DISK_DIR => CompletionItemKind::FOLDER, SymType::NAMESPACE => CompletionItemKind::FOLDER, diff --git a/server/src/features/features_utils.rs b/server/src/features/features_utils.rs index 401fe935..71b08ac6 100644 --- a/server/src/features/features_utils.rs +++ b/server/src/features/features_utils.rs @@ -473,6 +473,7 @@ impl FeaturesUtils { SymType::VARIABLE if symbol.as_variable().is_parameter => S!("parameter"), SymType::FUNCTION if symbol.as_func().is_property => S!("property"), SymType::FUNCTION if symbol.parent().unwrap().upgrade().unwrap().borrow().typ() == SymType::CLASS => S!("method"), + SymType::PACKAGE(_) => S!("package"), type_ => type_.to_string().to_lowercase() } }