diff --git a/server/Cargo.toml b/server/Cargo.toml index 1c13b7a2..7f4a263f 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -46,11 +46,11 @@ csv = "1.3.1" nix = { version = "0.29.0", features = ["process"] } [[bench]] -name = "iai_profiler" +name = "gungraun_bench" harness = false [dev-dependencies] -iai-callgrind = "0.14.2" +gungraun = "0.17.2" assert_fs = "1.1" [features] diff --git a/server/benches/iai_profiler.rs b/server/benches/gungraun_bench.rs similarity index 87% rename from server/benches/iai_profiler.rs rename to server/benches/gungraun_bench.rs index a847bb07..1ba715d7 100644 --- a/server/benches/iai_profiler.rs +++ b/server/benches/gungraun_bench.rs @@ -8,20 +8,20 @@ use tracing_subscriber::{fmt, FmtSubscriber, layer::SubscriberExt}; use std::env; -use iai_callgrind::{ - library_benchmark, library_benchmark_group, main, LibraryBenchmarkConfig, - FlamegraphConfig +use gungraun::{ + Callgrind, FlamegraphConfig, LibraryBenchmarkConfig, library_benchmark, library_benchmark_group, main }; use std::hint::black_box; /* -To run iai-callgrind: +To run gungraun-callgrind: install valgrind -run cargo install --version 0.14.0 iai-callgrind-runner +run cargo install --version 0.14.0 gungraun-runner +run cargo bench -- --nocapture */ #[library_benchmark] -fn iai_main() { +fn gungraun_main() { // TODO: Audit that the environment access only happens in single-threaded code. unsafe { env::set_var("RUST_BACKTRACE", "full") }; @@ -87,9 +87,11 @@ fn iai_main() { info!(">>>>>>>>>>>>>>>>>> End Session <<<<<<<<<<<<<<<<<<"); } -library_benchmark_group!(name = my_group; benchmarks = iai_main); +library_benchmark_group!(name = my_group; benchmarks = gungraun_main); main!( - config = LibraryBenchmarkConfig::default().flamegraph(FlamegraphConfig::default()); + config = LibraryBenchmarkConfig::default().tool(Callgrind::default() + .flamegraph(FlamegraphConfig::default()) + ), library_benchmark_groups = my_group ); \ No newline at end of file diff --git a/server/src/core/evaluation.rs b/server/src/core/evaluation.rs index a202fd31..3e41210e 100644 --- a/server/src/core/evaluation.rs +++ b/server/src/core/evaluation.rs @@ -2,7 +2,7 @@ use itertools::Itertools; use itertools::FoldWhile::{Continue, Done}; use ruff_python_ast::{Arguments, Expr, ExprCall, Identifier, Number, Operator, Parameter, UnaryOp}; use ruff_text_size::{Ranged, TextRange, TextSize}; -use lsp_types::{Diagnostic, Position, Range}; +use lsp_types::{Diagnostic, Location, Position, Range}; use weak_table::traits::WeakElement; use std::cmp::{max, min}; use std::collections::{HashMap, HashSet}; @@ -10,7 +10,8 @@ use std::i32; use std::rc::{Rc, Weak}; use std::cell::RefCell; use crate::core::diagnostics::{create_diagnostic, DiagnosticCode}; -use crate::{constants::*, Sy}; +use crate::utils::NoHashBuilder; +use crate::{Sy, constants::*, oyarn}; use crate::core::odoo::SyncOdoo; use crate::threads::SessionInfo; use crate::S; @@ -282,7 +283,7 @@ impl Evaluation { Evaluation { symbol: EvaluationSymbol { sym: EvaluationSymbolPtr::WEAK(EvaluationSymbolWeak{ - weak: Rc::downgrade(&odoo.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("list")]), u32::MAX).last().expect("builtins list not found")), + weak: odoo.get_ts_list(), context: HashMap::new(), instance: Some(true), is_super: false, @@ -298,7 +299,7 @@ impl Evaluation { Evaluation { symbol: EvaluationSymbol { sym: EvaluationSymbolPtr::WEAK(EvaluationSymbolWeak{ - weak: Rc::downgrade(&odoo.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("tuple")]), u32::MAX).last().expect("builtins list not found")), + weak: odoo.get_ts_tuple(), context: HashMap::new(), instance: Some(true), is_super: false, @@ -314,7 +315,7 @@ impl Evaluation { Evaluation { symbol: EvaluationSymbol { sym: EvaluationSymbolPtr::WEAK(EvaluationSymbolWeak{ - weak: Rc::downgrade(&odoo.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("dict")]), u32::MAX).last().expect("builtins list not found")), + weak: odoo.get_ts_dict(), context: HashMap::new(), instance: Some(true), is_super: false, @@ -330,7 +331,7 @@ impl Evaluation { Evaluation { symbol: EvaluationSymbol { sym: EvaluationSymbolPtr::WEAK(EvaluationSymbolWeak{ - weak: Rc::downgrade(&odoo.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("set")]), u32::MAX).last().expect("builtins set not found")), + weak: odoo.get_ts_set(), context: HashMap::new(), instance: Some(true), is_super: false, @@ -354,25 +355,25 @@ impl Evaluation { } pub fn new_constant(odoo: &mut SyncOdoo, values: Expr, range: TextRange) -> Evaluation { - let tree_value = match &values { + let symbol = match &values { Expr::StringLiteral(_s) => { - (vec![Sy!("builtins")], vec![Sy!("str")]) + odoo.get_ts_string() }, Expr::BooleanLiteral(_b) => { - (vec![Sy!("builtins")], vec![Sy!("bool")]) + odoo.get_ts_boolean() }, Expr::NumberLiteral(_n) => { match _n.value { - Number::Float(_) => (vec![Sy!("builtins")], vec![Sy!("float")]), - Number::Int(_) => (vec![Sy!("builtins")], vec![Sy!("int")]), - Number::Complex { .. } => (vec![Sy!("builtins")], vec![Sy!("complex")]), + Number::Float(_) => odoo.get_ts_float(), + Number::Int(_) => odoo.get_ts_int(), + Number::Complex { .. } => odoo.get_ts_complex(), } }, Expr::BytesLiteral(_b) => { - (vec![Sy!("builtins")], vec![Sy!("bytes")]) + odoo.get_ts_bytes() }, Expr::EllipsisLiteral(_e) => { - (vec![Sy!("builtins")], vec![Sy!("Ellipsis")]) + odoo.get_ts_ellipsis() }, Expr::NoneLiteral(_n) => { let mut eval = Evaluation::new_none(); @@ -380,14 +381,10 @@ impl Evaluation { eval.value = Some(EvaluationValue::CONSTANT(values)); return eval } - _ => {(vec![Sy!("builtins")], vec![Sy!("object")])} + _ => { + odoo.get_ts_object() + } }; - let symbol; - if !values.is_none_literal_expr() { - symbol = Rc::downgrade(&odoo.get_symbol("", &tree_value, u32::MAX).last().expect("builtins class not found")); - } else { - symbol = Weak::new(); - } Evaluation { symbol: EvaluationSymbol { sym: EvaluationSymbolPtr::WEAK(EvaluationSymbolWeak{ @@ -511,7 +508,7 @@ impl Evaluation { /// else: /// i="test" /// It will return two evaluation for i, one with 5 and one for "test" - pub fn from_sections(parent: &Symbol, sections: &HashMap>>>) -> Vec { + pub fn from_sections(parent: &Symbol, sections: &HashMap>>, NoHashBuilder>) -> Vec { let mut res = vec![]; let section = parent.as_symbol_mgr().get_section_for(u32::MAX); let content_symbols = parent.as_symbol_mgr()._get_loc_symbol(sections, u32::MAX, &SectionIndex::INDEX(section.index), &mut HashSet::new()); @@ -688,6 +685,7 @@ impl Evaluation { let mut evals = vec![]; let mut diagnostics = vec![]; let module = parent.borrow().find_module(); + let mut found_one_reference = false; match ast { ExprOrIdent::Expr(Expr::StringLiteral(expr)) => { @@ -1007,6 +1005,9 @@ impl Evaluation { Some(ContextValue::SYMBOL(s)) => s.clone(), _ => Weak::new() }; + if session.sync_odoo.evaluation_search.is_some() { + Evaluation::search_reference_in_arg(session, expr, &parent); + } if is_in_validation { let on_instance = base_sym_weak_eval.context.get(&S!("is_attr_of_instance")).map(|v| v.as_bool()); call_argument_diagnostics.last_mut().unwrap().extend(Evaluation::validate_call_arguments(session, @@ -1175,7 +1176,7 @@ impl Evaluation { let bases = Symbol::follow_ref(&base, session, &mut None, false, false, None, None); let value = Evaluation::expr_to_str(session, &sub.slice, parent.clone(), max_infer, false, &mut diagnostics); diagnostics.extend(value.1); - for base in bases.iter() { + for base in bases.iter() { match base { EvaluationSymbolPtr::WEAK(base_sym_weak_eval) if base_sym_weak_eval.instance == Some(false) => { if let Some(SymType::CLASS) = base.upgrade_weak().map(|s| s.borrow().typ()) { @@ -1266,11 +1267,13 @@ impl Evaluation { } }, ExprOrIdent::Expr(Expr::BinOp(operator)) => { - match operator.op { - Operator::Add => { - - }, - _ => {} + if odoo.evaluation_search.is_some() { + match operator.op { + Operator::Add | Operator::Mult | Operator::Sub | Operator::Div | Operator::BitAnd | Operator::BitOr | Operator::BitXor |Operator::FloorDiv | Operator::LShift | Operator::MatMult | Operator::Mod | Operator::Pow | Operator::RShift => { + Evaluation::eval_from_ast(session, &operator.left, parent.clone(), max_infer, false, required_dependencies); + Evaluation::eval_from_ast(session, &operator.right, parent.clone(), max_infer, false, required_dependencies); + } + } } }, ExprOrIdent::Expr(Expr::If(if_expr)) => { @@ -1292,7 +1295,7 @@ impl Evaluation { evals.push(Evaluation { symbol: EvaluationSymbol { sym: EvaluationSymbolPtr::WEAK(EvaluationSymbolWeak{ - weak: Rc::downgrade(&odoo.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("bool")]), u32::MAX).last().expect("builtins class not found")), + weak: odoo.get_ts_boolean(), context: HashMap::new(), instance: Some(true), is_super: false, @@ -1302,6 +1305,9 @@ impl Evaluation { value: None, range: Some(unary_operator.range()), }); + if odoo.evaluation_search.is_some() { //Still evaluate if we are searching for something + Evaluation::eval_from_ast(session, &unary_operator.operand, parent.clone(), max_infer, for_annotation, required_dependencies); + } break 'u_op_block }, }; @@ -1337,7 +1343,7 @@ impl Evaluation { Evaluation { symbol: EvaluationSymbol { sym: EvaluationSymbolPtr::WEAK(EvaluationSymbolWeak{ - weak: Rc::downgrade(&odoo.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("str")]), u32::MAX).last().expect("builtins class not found")), + weak: odoo.get_ts_string(), context: HashMap::new(), instance: Some(true), is_super: false, @@ -1351,6 +1357,65 @@ impl Evaluation { } _ => {} } + if let Some(evaluation_search) = session.sync_odoo.evaluation_search.as_ref() { + for eval in evals.iter() { + if eval.symbol.sym.is_weak() && let Some(weak) = eval.symbol.sym.as_weak().weak.upgrade() { + if Rc::ptr_eq(&weak, evaluation_search) { + if found_one_reference { + //if we have multiple matches, it means that that ast can reference it multiple times, but we only want to know if that ast matches or not + continue; + } + let file = parent.borrow().get_file().unwrap().upgrade().unwrap(); + let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(&file.borrow().paths()[0]); + if let Some(file_info) = file_info { + found_one_reference = true; + let range= ast.range(); + let transformed_range = file_info.borrow().text_range_to_range(&range, session.sync_odoo.encoding); + //check that a previous call to analyze_ast (recursively for ex) didn't already add this expression + let uri = FileMgr::pathname2uri(&file.borrow().paths().first().unwrap()); + if session.sync_odoo.evaluation_locations.is_empty() || + session.sync_odoo.evaluation_locations.last().unwrap().uri != uri || + session.sync_odoo.evaluation_locations.last().unwrap().range.start.line != transformed_range.start.line{ + session.sync_odoo.evaluation_locations.push(Location { + uri: uri, + range: transformed_range, + }); + } + } + } + } + if let Some(value) = eval.value.as_ref() { + match value { + EvaluationValue::CONSTANT(constant) => { + match constant { + Expr::StringLiteral(s) => { + if evaluation_search.borrow().typ() == SymType::CLASS { + let class_bw = evaluation_search.borrow(); + let class = class_bw.as_class_sym(); + if let Some(model_data) = class._model.as_ref() { + if oyarn!("{}", s.value.to_str()) == model_data.name { + let file = parent.borrow().get_file().unwrap().upgrade().unwrap(); + let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(&file.borrow().paths()[0]); + if let Some(file_info) = file_info { + let transformed_range = file_info.borrow().text_range_to_range(&s.range, session.sync_odoo.encoding); + let uri = FileMgr::pathname2uri(&file.borrow().paths().first().unwrap()); + session.sync_odoo.evaluation_locations.push(Location { + uri: uri, + range: transformed_range, + }); + } + } + } + } + }, + _ => {} + } + }, + _ => {} + } + } + } + } AnalyzeAstResult { evaluations: evals, diagnostics } } @@ -1735,6 +1800,18 @@ impl Evaluation { } diagnostics } + + fn search_reference_in_arg(session: &mut SessionInfo, expr_call: &ExprCall, parent: &Rc>) { + for arg in expr_call.arguments.args.iter() { + if arg.is_starred_expr() { + continue; + } + Evaluation::eval_from_ast(session, arg, parent.clone(), &expr_call.range.start(), false, &mut vec![]); + } + for arg in expr_call.arguments.keywords.iter() { + Evaluation::eval_from_ast(session, &arg.value, parent.clone(), &expr_call.range.start(), false, &mut vec![]); + } + } } impl EvaluationSymbol { diff --git a/server/src/core/odoo.rs b/server/src/core/odoo.rs index c6f24b15..e850280e 100644 --- a/server/src/core/odoo.rs +++ b/server/src/core/odoo.rs @@ -5,6 +5,7 @@ use crate::core::file_mgr::AstType; use crate::core::module_load_order::sort_by_load_order; use crate::core::xml_data::OdooData; use crate::core::xml_validation::XmlValidator; +use crate::features::declaration::DeclarationFeature; use crate::features::document_symbols::DocumentSymbolFeature; use crate::features::references::ReferenceFeature; use crate::features::workspace_symbols::WorkspaceSymbolFeature; @@ -21,12 +22,13 @@ use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use lsp_server::{ErrorCode, RequestId, ResponseError}; use lsp_types::notification::{Notification, Progress}; -use lsp_types::request::WorkDoneProgressCreate; +use lsp_types::request::{GotoDeclarationResponse, WorkDoneProgressCreate}; use lsp_types::*; use request::{RegisterCapability, Request, WorkspaceConfiguration}; use ruff_source_file::PositionEncoding; use serde_json::Value; use tracing::{error, warn, info, trace}; +use weak_table::traits::WeakElement; use std::collections::HashSet; use std::process::Command; @@ -61,6 +63,42 @@ pub enum InitState { ODOO_READY, } +#[derive(Debug)] +pub struct TypeshedWeakReferences { + dict: Weak>, + tuple: Weak>, + set: Weak>, + list: Weak>, + string: Weak>, + boolean: Weak>, + int: Weak>, + float: Weak>, + complex: Weak>, + ellipsis: Weak>, + bytes: Weak>, + object: Weak>, +} + +impl TypeshedWeakReferences { + + pub fn new() -> Self { + Self { + dict: Weak::new(), + tuple: Weak::new(), + set: Weak::new(), + list: Weak::new(), + string: Weak::new(), + boolean: Weak::new(), + int: Weak::new(), + float: Weak::new(), + complex: Weak::new(), + ellipsis: Weak::new(), + bytes: Weak::new(), + object: Weak::new(), + } + } +} + #[derive(Debug)] pub struct SyncOdoo { pub version_major: u32, @@ -99,6 +137,10 @@ pub struct SyncOdoo { pub capabilities: lsp_types::ClientCapabilities, pub encoding: PositionEncoding, pub opened_files: Vec, + pub evaluation_search: Option>>, //If set, any evaluation will be check against this value. If evaluation matches, location is kept in evaluation_locations + pub evaluation_locations: Vec, + pub typeshed_weak_cache: TypeshedWeakReferences, //cache of weak references to important typeshed symbols, to avoid having to look for them in the graph for each evaluation + pub test_mode: bool, } @@ -145,7 +187,9 @@ impl SyncOdoo { capabilities: lsp_types::ClientCapabilities::default(), encoding: PositionEncoding::Utf16, opened_files: vec![], - + evaluation_search: None, + evaluation_locations: vec![], + typeshed_weak_cache: TypeshedWeakReferences::new(), test_mode: false, }; sync_odoo @@ -1204,6 +1248,90 @@ impl SyncOdoo { module.as_module_package().get_xml_id(&oyarn!("{}", id_split.last().unwrap())) } + pub fn get_ts_dict(&mut self) -> Weak> { + if self.typeshed_weak_cache.dict.is_expired() { + self.typeshed_weak_cache.dict = Rc::downgrade(&self.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("dict")]), u32::MAX).last().unwrap().clone()); + } + self.typeshed_weak_cache.dict.clone() + } + + pub fn get_ts_tuple(&mut self) -> Weak> { + if self.typeshed_weak_cache.tuple.is_expired() { + self.typeshed_weak_cache.tuple = Rc::downgrade(&self.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("tuple")]), u32::MAX).last().unwrap().clone()); + } + self.typeshed_weak_cache.tuple.clone() + } + + pub fn get_ts_set(&mut self) -> Weak> { + if self.typeshed_weak_cache.set.is_expired() { + self.typeshed_weak_cache.set = Rc::downgrade(&self.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("set")]), u32::MAX).last().unwrap().clone()); + } + self.typeshed_weak_cache.set.clone() + } + + pub fn get_ts_list(&mut self) -> Weak> { + if self.typeshed_weak_cache.list.is_expired() { + self.typeshed_weak_cache.list = Rc::downgrade(&self.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("list")]), u32::MAX).last().unwrap().clone()); + } + self.typeshed_weak_cache.list.clone() + } + + pub fn get_ts_string(&mut self) -> Weak> { + if self.typeshed_weak_cache.string.is_expired() { + self.typeshed_weak_cache.string = Rc::downgrade(&self.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("str")]), u32::MAX).last().unwrap().clone()); + } + self.typeshed_weak_cache.string.clone() + } + + pub fn get_ts_boolean(&mut self) -> Weak> { + if self.typeshed_weak_cache.boolean.is_expired() { + self.typeshed_weak_cache.boolean = Rc::downgrade(&self.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("bool")]), u32::MAX).last().unwrap().clone()); + } + self.typeshed_weak_cache.boolean.clone() + } + + pub fn get_ts_int(&mut self) -> Weak> { + if self.typeshed_weak_cache.int.is_expired() { + self.typeshed_weak_cache.int = Rc::downgrade(&self.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("int")]), u32::MAX).last().unwrap().clone()); + } + self.typeshed_weak_cache.int.clone() + } + + pub fn get_ts_float(&mut self) -> Weak> { + if self.typeshed_weak_cache.float.is_expired() { + self.typeshed_weak_cache.float = Rc::downgrade(&self.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("float")]), u32::MAX).last().unwrap().clone()); + } + self.typeshed_weak_cache.float.clone() + } + + pub fn get_ts_complex(&mut self) -> Weak> { + if self.typeshed_weak_cache.complex.is_expired() { + self.typeshed_weak_cache.complex = Rc::downgrade(&self.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("complex")]), u32::MAX).last().unwrap().clone()); + } + self.typeshed_weak_cache.complex.clone() + } + + pub fn get_ts_ellipsis(&mut self) -> Weak> { + if self.typeshed_weak_cache.ellipsis.is_expired() { + self.typeshed_weak_cache.ellipsis = Rc::downgrade(&self.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("Ellipsis")]), u32::MAX).last().unwrap().clone()); + } + self.typeshed_weak_cache.ellipsis.clone() + } + + pub fn get_ts_bytes(&mut self) -> Weak> { + if self.typeshed_weak_cache.bytes.is_expired() { + self.typeshed_weak_cache.bytes = Rc::downgrade(&self.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("bytes")]), u32::MAX).last().unwrap().clone()); + } + self.typeshed_weak_cache.bytes.clone() + } + + pub fn get_ts_object(&mut self) -> Weak> { + if self.typeshed_weak_cache.object.is_expired() { + self.typeshed_weak_cache.object = Rc::downgrade(&self.get_symbol("", &(vec![Sy!("builtins")], vec![Sy!("object")]), u32::MAX).last().unwrap().clone()); + } + self.typeshed_weak_cache.object.clone() + } + } #[derive(Debug)] @@ -1426,10 +1554,18 @@ impl Odoo { } pub fn handle_goto_definition(session: &mut SessionInfo, params: GotoDefinitionParams) -> Result, ResponseError> { + return Odoo::handle_gotos(session, params, false) + } + + pub fn handle_goto_declaration(session: &mut SessionInfo, params: GotoDefinitionParams) -> Result, ResponseError> { + return Odoo::handle_gotos(session, params, true) + } + + fn handle_gotos(session: &mut SessionInfo, params: GotoDefinitionParams, is_declaration: bool) -> Result, ResponseError> { if session.sync_odoo.state_init == InitState::NOT_READY { return Ok(None); } - session.log_message(MessageType::INFO, format!("GoToDefinition requested on {} at {} - {}", + session.log_message(MessageType::INFO, format!("GoToDeclaration requested on {} at {} - {}", params.text_document_position_params.text_document.uri.to_string(), params.text_document_position_params.position.line, params.text_document_position_params.position.character)); @@ -1468,14 +1604,28 @@ impl Odoo { match ast_type { AstType::Python => { if file_info.borrow().file_info_ast.borrow().indexed_module.is_some() { - return Ok(DefinitionFeature::get_location(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + if is_declaration { + return Ok(DeclarationFeature::get_location(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + + } else { + return Ok(DefinitionFeature::get_location(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + } } }, AstType::Xml => { - return Ok(DefinitionFeature::get_location_xml(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + if is_declaration { + return Ok(DeclarationFeature::get_location_xml(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + + } else { + return Ok(DefinitionFeature::get_location_xml(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + } }, AstType::Csv => { - return Ok(DefinitionFeature::get_location_csv(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + if is_declaration { + return Ok(DeclarationFeature::get_location_csv(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + } else { + return Ok(DefinitionFeature::get_location_csv(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + } }, } } diff --git a/server/src/core/symbols/class_symbol.rs b/server/src/core/symbols/class_symbol.rs index c59740bd..620eef06 100644 --- a/server/src/core/symbols/class_symbol.rs +++ b/server/src/core/symbols/class_symbol.rs @@ -8,6 +8,7 @@ use crate::constants::{OYarn, SymType}; use crate::core::file_mgr::NoqaInfo; use crate::core::model::ModelData; use crate::Sy; +use crate::utils::NoHashBuilder; use super::symbol::Symbol; use super::symbol_mgr::{SectionRange, SymbolMgr}; @@ -30,7 +31,7 @@ pub struct ClassSymbol { //Trait SymbolMgr //--- Body symbols pub sections: Vec, - pub symbols: HashMap>>>>, + pub symbols: HashMap>>, NoHashBuilder>>, //--- dynamics variables pub ext_symbols: HashMap>>>, pub decl_ext_symbols: PtrWeakKeyHashMap>, HashMap>>>>> @@ -84,7 +85,7 @@ impl ClassSymbol { } pub fn add_symbol(&mut self, content: &Rc>, section: u32) { - let sections = self.symbols.entry(content.borrow().name().clone()).or_insert(HashMap::new()); + let sections = self.symbols.entry(content.borrow().name().clone()).or_insert(HashMap::default()); let section_vec = sections.entry(section).or_insert(vec![]); section_vec.push(content.clone()); } diff --git a/server/src/core/symbols/file_symbol.rs b/server/src/core/symbols/file_symbol.rs index f73727a0..5f1d5eb5 100644 --- a/server/src/core/symbols/file_symbol.rs +++ b/server/src/core/symbols/file_symbol.rs @@ -1,6 +1,6 @@ use weak_table::{PtrWeakHashSet, PtrWeakKeyHashMap}; -use crate::{constants::{BuildStatus, BuildSteps, OYarn}, core::{file_mgr::NoqaInfo, model::Model, xml_data::OdooData}, oyarn}; +use crate::{constants::{BuildStatus, BuildSteps, OYarn}, core::{file_mgr::NoqaInfo, model::Model, xml_data::OdooData}, oyarn, utils::NoHashBuilder}; use std::{cell::RefCell, collections::HashMap, rc::{Rc, Weak}}; use super::{symbol::Symbol, symbol_mgr::{SectionRange, SymbolMgr}}; @@ -28,7 +28,7 @@ pub struct FileSymbol { //Trait SymbolMgr pub sections: Vec, - pub symbols: HashMap>>>>, + pub symbols: HashMap>>, NoHashBuilder>>, //--- dynamics variables pub ext_symbols: HashMap>>>, pub decl_ext_symbols: PtrWeakKeyHashMap>, HashMap>>>>> @@ -66,7 +66,7 @@ impl FileSymbol { } pub fn add_symbol(&mut self, content: &Rc>, section: u32) { - let sections = self.symbols.entry(content.borrow().name().clone()).or_insert_with(|| HashMap::new()); + let sections = self.symbols.entry(content.borrow().name().clone()).or_insert_with(|| HashMap::default()); let section_vec = sections.entry(section).or_insert_with(|| vec![]); section_vec.push(content.clone()); } diff --git a/server/src/core/symbols/function_symbol.rs b/server/src/core/symbols/function_symbol.rs index a5a87c71..e7ef1af0 100644 --- a/server/src/core/symbols/function_symbol.rs +++ b/server/src/core/symbols/function_symbol.rs @@ -5,7 +5,7 @@ use ruff_python_ast::{AtomicNodeIndex, Expr, ExprCall}; use ruff_text_size::{TextRange, TextSize}; use weak_table::{PtrWeakHashSet, PtrWeakKeyHashMap}; -use crate::{constants::{BuildStatus, BuildSteps, OYarn, SymType}, core::{evaluation::{Context, Evaluation}, file_mgr::NoqaInfo, model::Model}, oyarn, threads::SessionInfo}; +use crate::{constants::{BuildStatus, BuildSteps, OYarn, SymType}, core::{evaluation::{Context, Evaluation}, file_mgr::NoqaInfo, model::Model}, oyarn, threads::SessionInfo, utils::NoHashBuilder}; use super::{symbol::Symbol, symbol_mgr::{SectionRange, SymbolMgr}}; @@ -55,7 +55,7 @@ pub struct FunctionSymbol { //Trait SymbolMgr //--- Body content pub sections: Vec, - pub symbols: HashMap>>>>, + pub symbols: HashMap>>, NoHashBuilder>>, //--- dynamics variables pub ext_symbols: HashMap>>>, pub decl_ext_symbols: PtrWeakKeyHashMap>, HashMap>>>>> @@ -102,7 +102,7 @@ impl FunctionSymbol { } pub fn add_symbol(&mut self, content: &Rc>, section: u32) { - let sections = self.symbols.entry(content.borrow().name().clone()).or_insert(HashMap::new()); + let sections = self.symbols.entry(content.borrow().name().clone()).or_insert(HashMap::default()); let section_vec = sections.entry(section).or_insert(vec![]); section_vec.push(content.clone()); } diff --git a/server/src/core/symbols/module_symbol.rs b/server/src/core/symbols/module_symbol.rs index 21ea0d44..f5c971a2 100644 --- a/server/src/core/symbols/module_symbol.rs +++ b/server/src/core/symbols/module_symbol.rs @@ -18,7 +18,7 @@ use crate::core::odoo::SyncOdoo; use crate::core::symbols::symbol::Symbol; use crate::core::symbols::symbol_mgr::SymbolMgr; use crate::threads::SessionInfo; -use crate::utils::PathSanitizer as _; +use crate::utils::{NoHashBuilder, PathSanitizer as _}; use crate::S; use std::path::PathBuf; use std::rc::{Rc, Weak}; @@ -61,7 +61,7 @@ pub struct ModuleSymbol { //Trait SymbolMgr pub sections: Vec, - pub symbols: HashMap>>>>, + pub symbols: HashMap>>, NoHashBuilder>>, //--- dynamics variables pub ext_symbols: HashMap>>>, pub decl_ext_symbols: PtrWeakKeyHashMap>, HashMap>>>>>, @@ -136,7 +136,7 @@ impl ModuleSymbol { } pub fn add_symbol(&mut self, content: &Rc>, section: u32) { - let sections = self.symbols.entry(content.borrow().name().clone()).or_insert_with(|| HashMap::new()); + let sections = self.symbols.entry(content.borrow().name().clone()).or_insert_with(|| HashMap::default()); let section_vec = sections.entry(section).or_insert_with(|| vec![]); section_vec.push(content.clone()); } diff --git a/server/src/core/symbols/package_symbol.rs b/server/src/core/symbols/package_symbol.rs index 8a1798b8..7ab98157 100644 --- a/server/src/core/symbols/package_symbol.rs +++ b/server/src/core/symbols/package_symbol.rs @@ -1,6 +1,6 @@ use weak_table::{PtrWeakHashSet, PtrWeakKeyHashMap}; -use crate::{constants::{BuildStatus, BuildSteps, OYarn}, core::{file_mgr::NoqaInfo, model::Model, xml_data::OdooData}, oyarn, threads::SessionInfo, S}; +use crate::{S, constants::{BuildStatus, BuildSteps, OYarn}, core::{file_mgr::NoqaInfo, model::Model, xml_data::OdooData}, oyarn, threads::SessionInfo, utils::NoHashBuilder}; use std::{cell::RefCell, collections::HashMap, path::PathBuf, rc::{Rc, Weak}}; use super::{module_symbol::ModuleSymbol, symbol::Symbol, symbol_mgr::{SectionRange, SymbolMgr}}; @@ -120,7 +120,7 @@ pub struct PythonPackageSymbol { //Trait SymbolMgr pub sections: Vec, - pub symbols: HashMap>>>>, + pub symbols: HashMap>>, NoHashBuilder>>, //--- dynamics variables pub ext_symbols: HashMap>>>, pub decl_ext_symbols: PtrWeakKeyHashMap>, HashMap>>>>> @@ -159,7 +159,7 @@ impl PythonPackageSymbol { } pub fn add_symbol(&mut self, content: &Rc>, section: u32) { - let sections = self.symbols.entry(content.borrow().name().clone()).or_insert(HashMap::new()); + let sections = self.symbols.entry(content.borrow().name().clone()).or_insert(HashMap::default()); let section_vec = sections.entry(section).or_insert(vec![]); section_vec.push(content.clone()); } diff --git a/server/src/core/symbols/symbol.rs b/server/src/core/symbols/symbol.rs index fb10a712..6af893f1 100644 --- a/server/src/core/symbols/symbol.rs +++ b/server/src/core/symbols/symbol.rs @@ -13,7 +13,7 @@ use crate::core::evaluation::{Context, ContextValue, Evaluation, EvaluationSymbo use crate::core::model::Model; use crate::core::odoo::SyncOdoo; use crate::threads::SessionInfo; -use crate::utils::{compare_semver, PathSanitizer as _}; +use crate::utils::{NoHashBuilder, PathSanitizer as _, compare_semver}; use crate::S; use core::panic; use std::cmp::Ordering; @@ -1121,7 +1121,7 @@ impl Symbol { } } - pub fn iter_symbols(&self) -> std::collections::hash_map::Iter<'_, OYarn, HashMap>>>> { + pub fn iter_symbols(&self) -> std::collections::hash_map::Iter<'_, OYarn, HashMap>>, NoHashBuilder>> { match self { Symbol::File(f) => { f.symbols.iter() @@ -2352,6 +2352,34 @@ impl Symbol { results } + pub fn follow_imported_ref(evaluation: &EvaluationSymbolPtr, session: &mut SessionInfo, context: &mut Option) -> Vec { + let mut res = vec![]; + let mut symbols = VecDeque::new(); + symbols.push_back(evaluation.clone()); + while let Some(current_sym) = symbols.pop_front() { + let EvaluationSymbolPtr::WEAK(w) = ¤t_sym else { + res.push(current_sym.clone()); + continue; + }; + let Some(symbol) = w.weak.upgrade() else { + res.push(current_sym.clone()); + continue; + }; + if symbol.borrow().typ() == SymType::VARIABLE && symbol.borrow().as_variable().is_import_variable { + let sym_b = symbol.borrow(); + let evaluations = sym_b.evaluations(); + if let Some(evals) = evaluations { + for eval in evals.iter() { + symbols.push_back(eval.symbol.get_symbol(session, context, &mut vec![], None)); + } + } + } else { + res.push(current_sym); + } + } + res + } + pub fn all_symbols(&self) -> impl Iterator>> + use<> { //return an iterator on all symbols of self. only symbols in symbols and module_symbols will //be returned. @@ -2849,7 +2877,7 @@ impl Symbol { ) -> (Vec>>, Vec) { let mut result: Vec>> = vec![]; let mut visited_symbols: PtrWeakHashSet>> = PtrWeakHashSet::new(); - let mut extend_result = |syms: Vec>>, result: &mut Vec>>, visited_symbols: &mut PtrWeakHashSet>>| { + let extend_result = |syms: Vec>>, result: &mut Vec>>, visited_symbols: &mut PtrWeakHashSet>>| { syms.iter().for_each(|sym|{ if !visited_symbols.contains(sym){ visited_symbols.insert(sym.clone()); diff --git a/server/src/core/symbols/symbol_mgr.rs b/server/src/core/symbols/symbol_mgr.rs index f6777416..58a2fbb5 100644 --- a/server/src/core/symbols/symbol_mgr.rs +++ b/server/src/core/symbols/symbol_mgr.rs @@ -3,6 +3,7 @@ use std::{cell::RefCell, rc::Rc, collections::{HashMap, HashSet}}; use ruff_text_size::TextSize; use crate::constants::OYarn; +use crate::utils::NoHashBuilder; use super::{class_symbol::ClassSymbol, file_symbol::FileSymbol, function_symbol::FunctionSymbol, module_symbol::ModuleSymbol, package_symbol::PythonPackageSymbol, symbol::Symbol}; @@ -34,7 +35,7 @@ pub trait SymbolMgr { fn change_parent(&mut self, new_parent: SectionIndex, section: &mut SectionRange); fn get_content_symbol(&self, name: OYarn, position: u32) -> ContentSymbols; fn _init_symbol_mgr(&mut self); - fn _get_loc_symbol(&self, map: &HashMap>>>, position: u32, index: &SectionIndex, acc: &mut HashSet) -> ContentSymbols; + fn _get_loc_symbol(&self, map: &HashMap>>, NoHashBuilder>, position: u32, index: &SectionIndex, acc: &mut HashSet) -> ContentSymbols; fn get_all_visible_symbols(&self, name_prefix: &String, position: u32) -> HashMap>>>; } @@ -101,7 +102,7 @@ macro_rules! impl_section_mgr_for { ///Return all the symbols that are valid as last declaration for the given position fn get_content_symbol(&self, name: OYarn, position: u32) -> ContentSymbols { - let sections: Option<&HashMap>>>> = self.symbols.get(&name); + let sections: Option<&HashMap>>, NoHashBuilder>> = self.symbols.get(&name); let mut content = if let Some(sections) = sections { let section: SectionRange = self.get_section_for(position); self._get_loc_symbol(sections, position, &SectionIndex::INDEX(section.index), &mut HashSet::new()) @@ -117,7 +118,7 @@ macro_rules! impl_section_mgr_for { } ///given all the sections of a symbol and a position, return all the Symbols that can represent the symbol - fn _get_loc_symbol(&self, map: &HashMap>>>, position: u32, index: &SectionIndex, acc: &mut HashSet) -> ContentSymbols { + fn _get_loc_symbol(&self, map: &HashMap>>, NoHashBuilder>, position: u32, index: &SectionIndex, acc: &mut HashSet) -> ContentSymbols { let mut res = ContentSymbols::default(); match index { SectionIndex::NONE => { return res; }, diff --git a/server/src/features/declaration.rs b/server/src/features/declaration.rs new file mode 100644 index 00000000..49dc7a36 --- /dev/null +++ b/server/src/features/declaration.rs @@ -0,0 +1,120 @@ +use lsp_types::request::GotoDeclarationResponse; +use lsp_types::{LocationLink, Range}; +use std::path::PathBuf; +use std::{cell::RefCell, rc::Rc}; + +use crate::constants::SymType; +use crate::core::file_mgr::{FileInfo, FileMgr}; +use crate::core::symbols::symbol::Symbol; +use crate::features::goto_utils::{GotoRequest, GotoUtils}; +use crate::features::xml_ast_utils::{XmlAstResult, XmlAstUtils}; +use crate::threads::SessionInfo; +use crate::utils::PathSanitizer as _; + +pub struct DeclarationFeature {} + +impl DeclarationFeature { + + pub fn get_location(session: &mut SessionInfo, + file_symbol: &Rc>, + file_info: &Rc>, + line: u32, + character: u32 + ) -> Option { + let definitions_sources = GotoUtils::get_symbols(session, GotoRequest::Declaration, file_symbol, file_info, line, character); + let mut links = vec![]; + for def in definitions_sources.iter() { + links.extend(GotoUtils::goto_source_to_location(session, def)); + } + Some(GotoDeclarationResponse::Link(links)) + } + + pub fn get_location_xml(session: &mut SessionInfo, + file_symbol: &Rc>, + file_info: &Rc>, + line: u32, + character: u32 + ) -> Option { + let offset = file_info.borrow().position_to_offset(line, character, session.sync_odoo.encoding); + let data = file_info.borrow().file_info_ast.borrow().text_document.as_ref().unwrap().contents().to_string(); + let document = roxmltree::Document::parse(&data); + if let Ok(document) = document { + let root = document.root_element(); + let (symbols, link_range) = XmlAstUtils::get_symbols(session, file_symbol, root, offset, true); + if symbols.is_empty() { + return None; + } + let mut links = vec![]; + for xml_result in symbols.iter() { + match xml_result { + crate::features::xml_ast_utils::XmlAstResult::SYMBOL(s) => { + if let Some(file) = s.borrow().get_file() { + for path in file.upgrade().unwrap().borrow().paths().iter() { + let full_path = match file.upgrade().unwrap().borrow().typ() { + SymType::PACKAGE(_) => PathBuf::from(path).join(format!("__init__.py{}", file.upgrade().unwrap().borrow().as_package().i_ext())).sanitize(), + _ => path.clone() + }; + let range = if s.borrow().has_range() { + session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, &full_path, &s.borrow().range()) + } else { + Range::default() + }; + let link_range = if link_range.is_some() { + Some(session.sync_odoo.get_file_mgr().borrow().std_range_to_range(session, file_symbol.borrow().paths().first().as_ref().unwrap(), link_range.as_ref().unwrap())) + } else { + None + }; + links.push(LocationLink{ + origin_selection_range: link_range, + target_uri: FileMgr::pathname2uri(&full_path), + target_range: range, + target_selection_range: range + }); + } + } + }, + XmlAstResult::XML_DATA(xml_file_symbol, range) => { + let file = xml_file_symbol.borrow().get_file(); //in case of XML_DATA coming from a python class + if let Some(file) = file { + if let Some(file) = file.upgrade() { + for path in file.borrow().paths().iter() { + let full_path = match file.borrow().typ() { + SymType::PACKAGE(_) => PathBuf::from(path).join(format!("__init__.py{}", file.borrow().as_package().i_ext())).sanitize(), + _ => path.clone() + }; + let range = match file.borrow().typ() { + SymType::PACKAGE(_) | SymType::FILE | SymType::NAMESPACE | SymType::DISK_DIR => Range::default(), + _ => session.sync_odoo.get_file_mgr().borrow().std_range_to_range(session, &full_path, &range), + }; + let link_range = if link_range.is_some() { + Some(session.sync_odoo.get_file_mgr().borrow().std_range_to_range(session, file_symbol.borrow().paths().first().as_ref().unwrap(), link_range.as_ref().unwrap())) + } else { + None + }; + links.push(LocationLink{ + origin_selection_range: link_range, + target_uri: FileMgr::pathname2uri(&full_path), + target_range: range, + target_selection_range: range + }); + } + } + } + } + } + } + return Some(GotoDeclarationResponse::Link(links)); + } + None + } + + pub fn get_location_csv(_session: &mut SessionInfo, + _file_symbol: &Rc>, + _file_info: &Rc>, + _line: u32, + _character: u32 + ) -> Option { + None + } + +} diff --git a/server/src/features/definition.rs b/server/src/features/definition.rs index aef6111c..151f8346 100644 --- a/server/src/features/definition.rs +++ b/server/src/features/definition.rs @@ -1,19 +1,12 @@ use lsp_types::{GotoDefinitionResponse, LocationLink, Range}; -use ruff_python_ast::{Expr, ExprCall}; -use ruff_text_size::TextSize; use std::path::PathBuf; use std::{cell::RefCell, rc::Rc}; -use crate::constants::{PackageType, SymType}; -use crate::core::evaluation::{Evaluation, EvaluationValue, ExprOrIdent}; +use crate::constants::SymType; use crate::core::file_mgr::{FileInfo, FileMgr}; -use crate::core::odoo::SyncOdoo; -use crate::core::python_odoo_builder::MAGIC_FIELDS; use crate::core::symbols::symbol::Symbol; -use crate::features::ast_utils::AstUtils; -use crate::features::features_utils::FeaturesUtils; +use crate::features::goto_utils::{GotoRequest, GotoUtils}; use crate::features::xml_ast_utils::{XmlAstResult, XmlAstUtils}; -use crate::{S, oyarn}; use crate::threads::SessionInfo; use crate::utils::PathSanitizer as _; @@ -21,291 +14,16 @@ pub struct DefinitionFeature {} impl DefinitionFeature { - fn check_for_domain_field(session: &mut SessionInfo, eval: &Evaluation, file_symbol: &Rc>, call_expr: &Option, offset: usize, links: &mut Vec) -> bool { - let (field_name, field_range) = if let Some(eval_value) = eval.value.as_ref() { - if let EvaluationValue::CONSTANT(Expr::StringLiteral(expr)) = eval_value { - (expr.value.to_string(), expr.range) - } else { - return false; - } - } else { - return false; - }; - let Some(call_expr) = call_expr else { return false }; - let module = file_symbol.borrow().find_module(); - let string_domain_fields = FeaturesUtils::find_argument_symbols( - session, Symbol::get_scope_symbol(file_symbol.clone(), offset as u32, false), module, &field_name, call_expr, offset, field_range - ); - string_domain_fields.iter().for_each(|(field, field_range)|{ - if let Some(file_sym) = field.borrow().get_file().and_then(|file_sym_weak| file_sym_weak.upgrade()){ - let path = file_sym.borrow().paths()[0].clone(); - let range = session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, &path, &field.borrow().range()); - links.push(LocationLink{ - origin_selection_range: Some(session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, file_symbol.borrow().paths().first().as_ref().unwrap(), &field_range)), - target_uri: FileMgr::pathname2uri(&path), - target_selection_range: range, - target_range: range, - }); - } - }); - string_domain_fields.len() > 0 - } - - fn check_for_model_string(session: &mut SessionInfo, eval: &Evaluation, file_symbol: &Rc>, links: &mut Vec) -> bool { - let value = if let Some(eval_value) = eval.value.as_ref() { - if let EvaluationValue::CONSTANT(Expr::StringLiteral(expr)) = eval_value { - oyarn!("{}", expr.value.to_string()) - } else { - return false; - } - } else { - return false; - }; - let model = session.sync_odoo.models.get(&value).cloned(); - let Some(model) = model else { - return false; - }; - let mut model_found = false; - let from_module = file_symbol.borrow().find_module(); - let classes = model.borrow().get_symbols(session, from_module.clone()); - let len_classes = classes.len(); - for class_symbol_rc in classes { - let class_symbol = class_symbol_rc.borrow(); - if let (Some(eval_range), Some(class_file)) = (eval.range, class_symbol.get_file().and_then(|file_sym_weak| file_sym_weak.upgrade())) { - if Rc::ptr_eq(file_symbol, &class_file) && class_symbol.range().contains(eval_range.start()) && len_classes > 1{ - continue; // if we are already on the class, skip, unless it is the only result - } - } - if let Some(model_file_sym) = class_symbol.get_file().and_then(|model_file_sym_weak| model_file_sym_weak.upgrade()){ - let path = model_file_sym.borrow().get_symbol_first_path(); - let range = session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, &path, &class_symbol.range()); - model_found = true; - links.push(LocationLink{ - origin_selection_range: eval.range.map(|r| session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, file_symbol.borrow().paths().first().as_ref().unwrap(), &r)), - target_uri: FileMgr::pathname2uri(&path), - target_selection_range: range, - target_range: range, - }); - } - } - model_found - } - - fn check_for_module_string(session: &mut SessionInfo, eval: &Evaluation, file_symbol: &Rc>, file_path: &String, links: &mut Vec) -> bool { - if file_symbol.borrow().typ() != SymType::PACKAGE(PackageType::MODULE) || !file_path.ends_with("__manifest__.py") { - // If not on manifest, we don't check for modules - return false; - }; - let value = if let Some(eval_value) = eval.value.as_ref() { - if let EvaluationValue::CONSTANT(Expr::StringLiteral(expr)) = eval_value { - oyarn!("{}", expr.value.to_string()) - } else { - return false; - } - } else { - return false; - }; - let Some(module) = session.sync_odoo.modules.get(&oyarn!("{}", value)).and_then(|m| m.upgrade()) else { - return false; - }; - let path = PathBuf::from(module.borrow().paths()[0].clone()).join("__manifest__.py").sanitize(); - links.push(LocationLink{ - origin_selection_range: None, - target_uri: FileMgr::pathname2uri(&path), - target_selection_range: Range::default(), - target_range: Range::default(), - }); - true - } - - fn check_for_xml_id_string(session: &mut SessionInfo, eval: &Evaluation, file_symbol: &Rc>, links: &mut Vec) -> bool { - let value = if let Some(eval_value) = eval.value.as_ref() { - if let EvaluationValue::CONSTANT(Expr::StringLiteral(expr)) = eval_value { - oyarn!("{}", expr.value.to_string()) - } else { - return false; - } - } else { - return false; - }; - let mut xml_found = false; - let xml_ids = SyncOdoo::get_xml_ids(session, file_symbol, value.as_str(), &std::ops::Range{start: 0, end: 0}, &mut vec![]); - for xml_id in xml_ids { - let file = xml_id.get_file_symbol(); - if let Some(file) = file { - if let Some(file) = file.upgrade() { - let range = session.sync_odoo.get_file_mgr().borrow().std_range_to_range(session, &file.borrow().paths()[0], &xml_id.get_range()); - xml_found = true; - links.push(LocationLink { - origin_selection_range: eval.range.map(|r| session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, file_symbol.borrow().paths().first().as_ref().unwrap(), &r)), - target_uri: FileMgr::pathname2uri(&file.borrow().paths()[0]), - target_range: range, - target_selection_range: range }); - } - } - } - xml_found - } - - fn check_for_compute_string(session: &mut SessionInfo, eval: &Evaluation, file_symbol: &Rc>, call_expr: &Option, offset: usize, links: &mut Vec) -> bool { - let value = if let Some(eval_value) = eval.value.as_ref() { - if let EvaluationValue::CONSTANT(Expr::StringLiteral(expr)) = eval_value { - expr.value.to_string() - } else { - return false; - } - } else { - return false; - }; - let Some(call_expr) = call_expr else { return false }; - let method_symbols = FeaturesUtils::find_kwarg_methods_symbols( - session, Symbol::get_scope_symbol(file_symbol.clone(), offset as u32, false), file_symbol.borrow().find_module(), &value, call_expr, &offset - ); - method_symbols.iter().for_each(|field|{ - if let Some(file_sym) = field.borrow().get_file().and_then(|file_sym_weak| file_sym_weak.upgrade()){ - let path = file_sym.borrow().paths()[0].clone(); - let range = session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, &path, &field.borrow().range()); - links.push(LocationLink{ - origin_selection_range: eval.range.map(|r| session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, file_symbol.borrow().paths().first().as_ref().unwrap(), &r)), - target_uri: FileMgr::pathname2uri(&path), - target_selection_range: range, - target_range: range, - }); - } - }); - method_symbols.len() > 0 - } - - pub fn add_display_name_compute_methods(session: &mut SessionInfo, links: &mut Vec, expr: &ExprOrIdent, file_symbol: &Rc>, offset: usize) { - // now we want `_compute_display_name` definition(s) - // we need the symbol of the model/ then we run get member symbol - // to do that, we need the expr, match it to attribute, get the value, get its evals - // with those evals, we run get_member_symbol on `_compute_display_name` - let crate::core::evaluation::ExprOrIdent::Expr(Expr::Attribute(attr_expr)) = expr else { - return; - }; - let (analyse_ast_result, _range) = AstUtils::get_symbol_from_expr(session, file_symbol, &crate::core::evaluation::ExprOrIdent::Expr(&attr_expr.value), offset as u32); - let eval_ptrs = analyse_ast_result.evaluations.iter().flat_map(|eval| Symbol::follow_ref(eval.symbol.get_symbol_ptr(), session, &mut None, false, false, None, None)).collect::>(); - let maybe_module = file_symbol.borrow().find_module(); - let symbols = eval_ptrs.iter().flat_map(|eval_ptr| { - let Some(symbol) = eval_ptr.upgrade_weak() else { - return vec![]; - }; - symbol.borrow().get_member_symbol(session, &S!("_compute_display_name"), maybe_module.clone(), false, false, true, true, false).0 - }).collect::>(); - for symbol in symbols { - if let Some(file) = symbol.borrow().get_file() { - for path in file.upgrade().unwrap().borrow().paths().iter() { - let full_path = match file.upgrade().unwrap().borrow().typ() { - SymType::PACKAGE(_) => PathBuf::from(path).join(format!("__init__.py{}", file.upgrade().unwrap().borrow().as_package().i_ext())).sanitize(), - _ => path.clone() - }; - let range = if symbol.borrow().has_range() { - if symbol.borrow().range().contains(TextSize::new(offset as u32)) { - continue; //skip if we are already on the definition - } - session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, &full_path, &symbol.borrow().range()) - } else { - Range::default() - }; - links.push(LocationLink{ - origin_selection_range: None, - target_uri: FileMgr::pathname2uri(&full_path), - target_selection_range: range, - target_range: range, - }); - } - } - } - } - pub fn get_location(session: &mut SessionInfo, file_symbol: &Rc>, file_info: &Rc>, line: u32, character: u32 ) -> Option { - let offset = file_info.borrow().position_to_offset(line, character, session.sync_odoo.encoding); - let file_info_ast_clone = file_info.borrow().file_info_ast.clone(); - let file_info_ast_ref = file_info_ast_clone.borrow(); - let (analyse_ast_result, _range, expr, call_expr) = AstUtils::get_symbols(session, &file_info_ast_ref, file_symbol, offset as u32); - if analyse_ast_result.evaluations.is_empty() { - return None; - } + let definitions_sources = GotoUtils::get_symbols(session, GotoRequest::Definition, file_symbol, file_info, line, character); let mut links = vec![]; - let mut evaluations = analyse_ast_result.evaluations.clone(); - // Filter out magic fields - let mut dislay_name_found = false; - evaluations.retain(|eval| { - // Filter out, variables, whose parents are a class, whose name is one of the magic fields, and have the same range as their parent - let eval_sym = eval.symbol.get_symbol(session, &mut None, &mut vec![], None); - let Some(eval_sym) = eval_sym.upgrade_weak() else { return true; }; - if !MAGIC_FIELDS.contains(&eval_sym.borrow().name().as_str()) || eval_sym.borrow().typ() != SymType::VARIABLE || !eval_sym.borrow().is_field(session) { - return true; - } - if eval_sym.borrow().name() == "display_name" { - dislay_name_found = true; - } - let Some(parent_sym) = eval_sym.borrow().parent().and_then(|parent| parent.upgrade()) else { return true; }; - if parent_sym.borrow().typ() != SymType::CLASS { - return true; - } - eval_sym.borrow().range() != parent_sym.borrow().range() - }); - if let Some(expr) = expr && dislay_name_found { - DefinitionFeature::add_display_name_compute_methods(session, &mut links, &expr, file_symbol, offset); - } - drop(file_info_ast_ref); - let mut index = 0; - while index < evaluations.len() { - let eval = evaluations[index].clone(); - if DefinitionFeature::check_for_domain_field(session, &eval, file_symbol, &call_expr, offset, &mut links) || - DefinitionFeature::check_for_compute_string(session, &eval, file_symbol,&call_expr, offset, &mut links) || - DefinitionFeature::check_for_module_string(session, &eval, file_symbol, &file_info.borrow().uri, &mut links) || - DefinitionFeature::check_for_model_string(session, &eval, file_symbol, &mut links) || - DefinitionFeature::check_for_xml_id_string(session, &eval, file_symbol, &mut links) { - index += 1; - continue; - } - if matches!(eval.value, Some(EvaluationValue::CONSTANT(_))) { - // Skip go to definition on literals - index += 1; - continue; - } - let Some(symbol) = eval.symbol.get_symbol_as_weak(session, &mut None, &mut vec![], None).weak.upgrade() else { - index += 1; - continue; - }; - if let Some(file) = symbol.borrow().get_file() { - //For import variable, we should take the next evaluation if we are at the same location than the offset, as the get_symbol will return the current import variable (special case as the definition is from outside the file) - if symbol.borrow().typ() == SymType::VARIABLE && symbol.borrow().as_variable().is_import_variable && Rc::ptr_eq(&file.upgrade().unwrap(), file_symbol) && symbol.borrow().has_range() && symbol.borrow().range().contains(TextSize::new(offset as u32)) { - evaluations.remove(index); - let symbol = symbol.borrow(); - let sym_eval = symbol.evaluations(); - if let Some(sym_eval) = sym_eval { - evaluations = [evaluations.clone(), sym_eval.clone()].concat(); - } - continue; - } - for path in file.upgrade().unwrap().borrow().paths().iter() { - let full_path = match file.upgrade().unwrap().borrow().typ() { - SymType::PACKAGE(_) => PathBuf::from(path).join(format!("__init__.py{}", file.upgrade().unwrap().borrow().as_package().i_ext())).sanitize(), - _ => path.clone() - }; - let range = if symbol.borrow().has_range() { - session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, &full_path, &symbol.borrow().range()) - } else { - Range::default() - }; - links.push(LocationLink{ - origin_selection_range: None, - target_uri: FileMgr::pathname2uri(&full_path), - target_selection_range: range, - target_range: range, - }); - } - } - index += 1; + for def in definitions_sources.iter() { + links.extend(GotoUtils::goto_source_to_location(session, def)); } Some(GotoDefinitionResponse::Link(links)) } diff --git a/server/src/features/goto_utils.rs b/server/src/features/goto_utils.rs new file mode 100644 index 00000000..42cc8ca0 --- /dev/null +++ b/server/src/features/goto_utils.rs @@ -0,0 +1,322 @@ +use std::{cell::RefCell, path::PathBuf, rc::Rc}; + +use lsp_types::{LocationLink, Range}; +use ruff_python_ast::{Expr, ExprCall}; + +use crate::{S, constants::{PackageType, SymType}, core::{evaluation::{Evaluation, EvaluationValue, ExprOrIdent}, file_mgr::{FileInfo, FileMgr}, odoo::SyncOdoo, python_odoo_builder::MAGIC_FIELDS, symbols::symbol::Symbol, xml_data::OdooData}, features::{ast_utils::AstUtils, features_utils::FeaturesUtils}, oyarn, threads::SessionInfo, utils::PathSanitizer}; + +pub enum GotoRequest { + Definition, + Declaration, +} + +pub enum GotoSourceType { + Symbol(Rc>), + OdooData(OdooData), + Module(Rc>), +} + +pub struct GotoSource { + pub source: GotoSourceType, + pub origin_selection_range: Option, +} +pub struct GotoUtils {} + +impl GotoUtils { + fn check_for_domain_field(session: &mut SessionInfo, eval: &Evaluation, file_symbol: &Rc>, call_expr: &Option, offset: usize, sources: &mut Vec) -> bool { + let (field_name, field_range) = if let Some(eval_value) = eval.value.as_ref() { + if let EvaluationValue::CONSTANT(Expr::StringLiteral(expr)) = eval_value { + (expr.value.to_string(), expr.range) + } else { + return false; + } + } else { + return false; + }; + let Some(call_expr) = call_expr else { return false }; + let module = file_symbol.borrow().find_module(); + let string_domain_fields = FeaturesUtils::find_argument_symbols( + session, Symbol::get_scope_symbol(file_symbol.clone(), offset as u32, false), module, &field_name, call_expr, offset, field_range + ); + let mut domain_found = false; + string_domain_fields.iter().for_each(|(field, field_range)|{ + if let Some(_) = field.borrow().get_file().and_then(|file_sym_weak| file_sym_weak.upgrade()){ + domain_found = true; + sources.push(GotoSource { + source: + GotoSourceType::Symbol(field.clone()), + origin_selection_range: Some(session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, file_symbol.borrow().paths().first().as_ref().unwrap(), &field_range)) + }); + } + }); + domain_found + } + + fn check_for_model_string(session: &mut SessionInfo, eval: &Evaluation, file_symbol: &Rc>, sources: &mut Vec) -> bool { + let value = if let Some(eval_value) = eval.value.as_ref() { + if let EvaluationValue::CONSTANT(Expr::StringLiteral(expr)) = eval_value { + oyarn!("{}", expr.value.to_string()) + } else { + return false; + } + } else { + return false; + }; + let model = session.sync_odoo.models.get(&value).cloned(); + let Some(model) = model else { + return false; + }; + let mut model_found = false; + let from_module = file_symbol.borrow().find_module(); + let classes = model.borrow().get_symbols(session, from_module.clone()); + let len_classes = classes.len(); + for class_symbol_rc in classes { + let class_symbol = class_symbol_rc.borrow(); + if let (Some(eval_range), Some(class_file)) = (eval.range, class_symbol.get_file().and_then(|file_sym_weak| file_sym_weak.upgrade())) { + if Rc::ptr_eq(file_symbol, &class_file) && class_symbol.range().contains(eval_range.start()) && len_classes > 1{ + continue; // if we are already on the class, skip, unless it is the only result + } + } + model_found = true; + sources.push(GotoSource { + source: GotoSourceType::Symbol(class_symbol_rc.clone()), + origin_selection_range: eval.range.map(|r| session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, file_symbol.borrow().paths().first().as_ref().unwrap(), &r)) + }); + } + model_found + } + + fn check_for_module_string(session: &mut SessionInfo, eval: &Evaluation, file_symbol: &Rc>, file_path: &String, sources: &mut Vec) -> bool { + if file_symbol.borrow().typ() != SymType::PACKAGE(PackageType::MODULE) || !file_path.ends_with("__manifest__.py") { + // If not on manifest, we don't check for modules + return false; + }; + let value = if let Some(eval_value) = eval.value.as_ref() { + if let EvaluationValue::CONSTANT(Expr::StringLiteral(expr)) = eval_value { + oyarn!("{}", expr.value.to_string()) + } else { + return false; + } + } else { + return false; + }; + let Some(module) = session.sync_odoo.modules.get(&oyarn!("{}", value)).and_then(|m| m.upgrade()) else { + return false; + }; + sources.push(GotoSource{ + source: GotoSourceType::Module(module.clone()), + origin_selection_range: None, + }); + true + } + + fn check_for_xml_id_string(session: &mut SessionInfo, eval: &Evaluation, file_symbol: &Rc>, sources: &mut Vec) -> bool { + let value = if let Some(eval_value) = eval.value.as_ref() { + if let EvaluationValue::CONSTANT(Expr::StringLiteral(expr)) = eval_value { + oyarn!("{}", expr.value.to_string()) + } else { + return false; + } + } else { + return false; + }; + let mut xml_found = false; + let xml_ids = SyncOdoo::get_xml_ids(session, file_symbol, value.as_str(), &std::ops::Range{start: 0, end: 0}, &mut vec![]); + for xml_id in xml_ids { + let file = xml_id.get_file_symbol(); + if let Some(file) = file { + if let Some(_file) = file.upgrade() { //test that file is valid to set xml_found to true + xml_found = true; + sources.push(GotoSource{ + source: GotoSourceType::OdooData(xml_id.clone()), + origin_selection_range: eval.range.map(|r| session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, file_symbol.borrow().paths().first().as_ref().unwrap(), &r)) + }); + } + } + } + xml_found + } + + fn check_for_compute_string(session: &mut SessionInfo, eval: &Evaluation, file_symbol: &Rc>, call_expr: &Option, offset: usize, sources: &mut Vec) -> bool { + let value = if let Some(eval_value) = eval.value.as_ref() { + if let EvaluationValue::CONSTANT(Expr::StringLiteral(expr)) = eval_value { + expr.value.to_string() + } else { + return false; + } + } else { + return false; + }; + let Some(call_expr) = call_expr else { return false }; + let method_symbols = FeaturesUtils::find_kwarg_methods_symbols( + session, Symbol::get_scope_symbol(file_symbol.clone(), offset as u32, false), file_symbol.borrow().find_module(), &value, call_expr, &offset + ); + let mut method_found = false; + method_symbols.iter().for_each(|field|{ + if let Some(file_sym) = field.borrow().get_file().and_then(|file_sym_weak| file_sym_weak.upgrade()){ + method_found = true; + sources.push(GotoSource { + source: GotoSourceType::Symbol(field.clone()), + origin_selection_range: eval.range.map(|r| session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, file_sym.borrow().paths().first().as_ref().unwrap(), &r)) + }); + } + }); + method_found + } + + pub fn add_display_name_compute_methods(session: &mut SessionInfo, sources: &mut Vec, expr: &ExprOrIdent, file_symbol: &Rc>, offset: usize) { + // now we want `_compute_display_name` definition(s) + // we need the symbol of the model/ then we run get member symbol + // to do that, we need the expr, match it to attribute, get the value, get its evals + // with those evals, we run get_member_symbol on `_compute_display_name` + let crate::core::evaluation::ExprOrIdent::Expr(Expr::Attribute(attr_expr)) = expr else { + return; + }; + let (analyse_ast_result, _range) = AstUtils::get_symbol_from_expr(session, file_symbol, &crate::core::evaluation::ExprOrIdent::Expr(&attr_expr.value), offset as u32); + let eval_ptrs = analyse_ast_result.evaluations.iter().flat_map(|eval| Symbol::follow_ref(eval.symbol.get_symbol_ptr(), session, &mut None, false, false, None, None)).collect::>(); + let maybe_module = file_symbol.borrow().find_module(); + let symbols = eval_ptrs.iter().flat_map(|eval_ptr| { + let Some(symbol) = eval_ptr.upgrade_weak() else { + return vec![]; + }; + symbol.borrow().get_member_symbol(session, &S!("_compute_display_name"), maybe_module.clone(), false, false, true, true, false).0 + }).collect::>(); + for symbol in symbols { + sources.push(GotoSource { + source: GotoSourceType::Symbol(symbol.clone()), + origin_selection_range: None, + }); + } + } + + pub fn get_symbols(session: &mut SessionInfo, + goto_request: GotoRequest, + file_symbol: &Rc>, + file_info: &Rc>, + line: u32, + character: u32 + ) -> Vec { + let offset = file_info.borrow().position_to_offset(line, character, session.sync_odoo.encoding); + let file_info_ast_clone = file_info.borrow().file_info_ast.clone(); + let file_info_ast_ref = file_info_ast_clone.borrow(); + let (analyse_ast_result, _range, expr, call_expr) = AstUtils::get_symbols(session, &file_info_ast_ref, file_symbol, offset as u32); + if analyse_ast_result.evaluations.is_empty() { + return vec![]; + } + let mut definition_sources = vec![]; + let mut evaluations = analyse_ast_result.evaluations.clone(); + // Filter out magic fields + let mut dislay_name_found = false; + evaluations.retain(|eval| { + // Filter out, variables, whose parents are a class, whose name is one of the magic fields, and have the same range as their parent + let eval_sym = eval.symbol.get_symbol(session, &mut None, &mut vec![], None); + let Some(eval_sym) = eval_sym.upgrade_weak() else { return true; }; + if !MAGIC_FIELDS.contains(&eval_sym.borrow().name().as_str()) || eval_sym.borrow().typ() != SymType::VARIABLE || !eval_sym.borrow().is_field(session) { + return true; + } + if eval_sym.borrow().name() == "display_name" { + dislay_name_found = true; + } + let Some(parent_sym) = eval_sym.borrow().parent().and_then(|parent| parent.upgrade()) else { return true; }; + if parent_sym.borrow().typ() != SymType::CLASS { + return true; + } + eval_sym.borrow().range() != parent_sym.borrow().range() + }); + if let Some(expr) = expr && dislay_name_found { + GotoUtils::add_display_name_compute_methods(session, &mut definition_sources, &expr, file_symbol, offset); + } + drop(file_info_ast_ref); + let mut index = 0; + while index < evaluations.len() { + let eval = evaluations[index].clone(); + if GotoUtils::check_for_domain_field(session, &eval, file_symbol, &call_expr, offset, &mut definition_sources) || + GotoUtils::check_for_compute_string(session, &eval, file_symbol,&call_expr, offset, &mut definition_sources) || + GotoUtils::check_for_module_string(session, &eval, file_symbol, &file_info.borrow().uri, &mut definition_sources) || + GotoUtils::check_for_model_string(session, &eval, file_symbol, &mut definition_sources) || + GotoUtils::check_for_xml_id_string(session, &eval, file_symbol, &mut definition_sources) { + index += 1; + continue; + } + if matches!(eval.value, Some(EvaluationValue::CONSTANT(_))) { + // Skip go to definition on literals + index += 1; + continue; + } + if matches!(goto_request, GotoRequest::Definition) { + let eval_ptr = eval.symbol.get_symbol(session, &mut None, &mut vec![], None); + let end_symbols = Symbol::follow_imported_ref(&eval_ptr, session, &mut None); + for end_symbol in end_symbols.iter() { + if let Some(symbol) = end_symbol.upgrade_weak() { + definition_sources.push(GotoSource{ + source: GotoSourceType::Symbol(symbol.clone()), + origin_selection_range: None + }); + } + } + } else { + let Some(symbol) = eval.symbol.get_symbol_as_weak(session, &mut None, &mut vec![], None).weak.upgrade() else { + index += 1; + continue; + }; + definition_sources.push(GotoSource{ + source: GotoSourceType::Symbol(symbol.clone()), + origin_selection_range: None + }); + } + index += 1; + } + definition_sources + } + + pub fn goto_source_to_location(session: &mut SessionInfo, def: &GotoSource) -> Vec { + let mut res = vec![]; + match &def.source { + GotoSourceType::Symbol(symbol) => { + if let Some(file_symbol) = symbol.borrow().get_file().and_then(|file_sym_weak| file_sym_weak.upgrade()){ + let paths = file_symbol.borrow().paths(); + for path in paths.iter() { + let full_path = match file_symbol.borrow().typ() { + SymType::PACKAGE(_) => PathBuf::from(path).join(format!("__init__.py{}", file_symbol.borrow().as_package().i_ext())).sanitize(), + _ => path.clone() + }; + let symbol_range = if symbol.borrow().has_range() { + session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, &full_path, &symbol.borrow().range()) + } else { + Range::default() + }; + res.push(LocationLink{ + origin_selection_range: def.origin_selection_range.clone(), + target_uri: FileMgr::pathname2uri(&full_path), + target_selection_range: symbol_range, + target_range: symbol_range, + }); + } + } + }, + GotoSourceType::Module(module) => { + let path = PathBuf::from(module.borrow().paths()[0].clone()).join("__manifest__.py").sanitize(); + res.push(LocationLink{ + origin_selection_range: None, + target_uri: FileMgr::pathname2uri(&path), + target_selection_range: Range::default(), + target_range: Range::default(), + }); + }, + GotoSourceType::OdooData(xml_id) => { + let file = xml_id.get_file_symbol(); + if let Some(file) = file { + if let Some(file) = file.upgrade() { + let range = session.sync_odoo.get_file_mgr().borrow().std_range_to_range(session, &file.borrow().paths()[0], &xml_id.get_range()); + res.push(LocationLink { + origin_selection_range: def.origin_selection_range.clone(), + target_uri: FileMgr::pathname2uri(&file.borrow().paths()[0]), + target_range: range, + target_selection_range: range }); + } + } + } + } + res + } +} \ No newline at end of file diff --git a/server/src/features/mod.rs b/server/src/features/mod.rs index e64f22f5..0a252445 100644 --- a/server/src/features/mod.rs +++ b/server/src/features/mod.rs @@ -1,8 +1,10 @@ pub mod ast_utils; pub mod completion; +pub mod declaration; pub mod definition; pub mod document_symbols; pub mod features_utils; +pub mod goto_utils; pub mod hover; pub mod node_index_ast; pub mod references; diff --git a/server/src/features/references.rs b/server/src/features/references.rs index e568e660..7eb162c7 100644 --- a/server/src/features/references.rs +++ b/server/src/features/references.rs @@ -1,19 +1,97 @@ +use std::rc::Weak; use std::{cell::RefCell, path::PathBuf, rc::Rc}; use lsp_types::{Location, Range}; +use ruff_python_ast::{Alias, Expr, Identifier, Stmt, StmtAnnAssign, StmtAssert, StmtAssign, StmtAugAssign, StmtClassDef, StmtIf, StmtMatch, StmtRaise, StmtReturn, StmtTry, StmtTypeAlias, StmtWith}; +use ruff_text_size::{Ranged, TextRange, TextSize}; +use weak_table::PtrWeakHashSet; +use crate::S; +use crate::constants::OYarn; +use crate::core::evaluation::{Evaluation, EvaluationSymbolPtr}; +use crate::core::odoo::SyncOdoo; +use crate::features::goto_utils::{GotoRequest, GotoSourceType, GotoUtils}; use crate::{constants::SymType, core::{file_mgr::{FileInfo, FileMgr}, symbols::symbol::Symbol}, features::xml_ast_utils::{XmlAstResult, XmlAstUtils}, threads::SessionInfo, utils::PathSanitizer}; - - pub struct ReferenceFeature { } impl ReferenceFeature { - pub fn get_references(_session: &mut SessionInfo, _file_symbol: &Rc>, _file_info: &Rc>, _line: u32, _character: u32) -> Option> { - // Implementation for getting references - None + /// Basic find all references implementation + /// Same-file references only, local vars, params, methods + /// TODO: Cross-file references within module + /// TODO: All files within workspace + /// TODO: Odoo specific (XML field refs, string-based model refs) + pub fn get_references(session: &mut SessionInfo, file_symbol: &Rc>, file_info: &Rc>, line: u32, character: u32) -> Option> { + //We want to search for references of the definition, and not the current symbol. Let's use definition feature for that + SyncOdoo::process_rebuilds(session, false); + let def_sources = GotoUtils::get_symbols(session, GotoRequest::Definition, file_symbol, file_info, line, character); + + let mut locations = Vec::new(); + for definition in def_sources.iter() { + match &definition.source { + GotoSourceType::Symbol(target_symbol) => { + let mut to_check: PtrWeakHashSet>> = PtrWeakHashSet::new(); + + to_check.insert(file_symbol.clone()); + + //take arch and arch_eval dependents + if !file_symbol.borrow().dependents().is_empty() { // file could be out of workspace + for dep in file_symbol.borrow().dependents()[0].iter().take(2) { + if let Some(dep_set) = dep { + for dep_symbol_rc in dep_set.iter() { + to_check.insert(dep_symbol_rc.clone()); + } + } + } + } + //If the symbol is a model, browse model dependents too + if target_symbol.borrow().typ() == SymType::CLASS { + if let Some(model_data) = target_symbol.borrow().as_class_sym()._model.as_ref() { + if let Some(model) = session.sync_odoo.models.get(&model_data.name).cloned() { + to_check.extend(model.borrow().dependents.clone()); + for symbol in model.borrow().all_symbols(session, None, false) { + to_check.insert(symbol.0); + } + } + } + } + let mut files_to_check: PtrWeakHashSet>> = PtrWeakHashSet::new(); //only iter on files + for sym in to_check.iter() { + let Some(Some(file)) = sym.borrow().get_file().map(|x| x.upgrade()) else { //to be sure we are on a file + continue; + }; + files_to_check.insert(file.clone()); + } + for file in files_to_check.iter() { + let Some(dep_file_info) = session.sync_odoo.get_file_mgr().borrow().get_file_info(&file.borrow().paths()[0]) else { + continue; + }; + locations.extend(ReferenceFeature::references_in_file(session, &file, &dep_file_info, &target_symbol)); + } + }, + _ => continue, + } + } + + if locations.is_empty() { + None + } else { + Some(locations) + } + } + + fn references_in_file(session: &mut SessionInfo, file_symbol: &Rc>, file_info: &Rc>, target_symbol_rc: &Rc>) -> Vec { + let file_info_ast = file_info.borrow().file_info_ast.clone(); + let mut visitor = ReferenceVisitor { + sym_stack: vec![], + }; + session.sync_odoo.evaluation_locations = vec![]; + session.sync_odoo.evaluation_search = Some(target_symbol_rc.clone()); + visitor.browse_file(session, file_symbol, file_info_ast.borrow().get_stmts().as_ref().unwrap()); + session.sync_odoo.evaluation_search = None; + std::mem::take(&mut session.sync_odoo.evaluation_locations) } pub fn get_references_xml(session: &mut SessionInfo, file_symbol: &Rc>, file_info: &Rc>, line: u32, character: u32) -> Option> { @@ -67,4 +145,206 @@ impl ReferenceFeature { pub fn get_references_csv(_session: &mut SessionInfo, _file_symbol: &Rc>, _file_info: &Rc>, _line: u32, _character: u32) -> Option> { None } +} + +struct ReferenceVisitor { + sym_stack: Vec>>, +} + +impl ReferenceVisitor { + + pub fn browse_file(&mut self, session: &mut SessionInfo, file_symbol: &Rc>, stmts: &Vec) { + self.sym_stack.push(file_symbol.clone()); + self.visit_vec_stmt(session, stmts); + } + + pub fn visit_vec_stmt(&mut self, session: &mut SessionInfo, vec_ast: &Vec) { + for stmt in vec_ast.iter() { + match stmt { + Stmt::FunctionDef(f) => { + let sym = self.sym_stack.last().unwrap().borrow().get_positioned_symbol(&OYarn::from(f.name.to_string()), &f.range); + if let Some(sym) = sym { + self.sym_stack.push(sym); + self.visit_vec_stmt(session, &f.body); + self.sym_stack.pop(); + } + }, + Stmt::ClassDef(c) => { + self.visit_class_def(session, c); + }, + Stmt::Try(t) => { + self.visit_try(session, t); + }, + Stmt::Import(i) => { + self._resolve_import(session, None, &i.names, None, &i.range); + }, + Stmt::ImportFrom(i) => { + self._resolve_import(session, i.module.as_ref(), &i.names, Some(i.level), &i.range); + }, + Stmt::Assign(a) => { + self.visit_assign(session, a); + }, + Stmt::AnnAssign(a) => { + self.visit_ann_assign(session, a); + }, + Stmt::Expr(e) => { + self.visit_expr(session, &e.value, &e.value.start()); + }, + Stmt::If(i) => { + self.visit_if(session, i); + }, + Stmt::Break(_) => {}, + Stmt::Continue(_) => {}, + Stmt::Delete(d) => { + for target in d.targets.iter() { + self.visit_expr(session, target, &target.start()); + } + }, + Stmt::For(f) => { + self.visit_expr(session, &f.target, &f.target.start()); + self.visit_vec_stmt(session, &f.body); + self.visit_vec_stmt(session, &f.orelse); + }, + Stmt::While(w) => { + self.visit_expr(session, &w.test, &w.test.start()); + self.visit_vec_stmt(session, &w.body); + self.visit_vec_stmt(session, &w.orelse); + }, + Stmt::Return(stmt_return) => self.visit_return_stmt(session, stmt_return), + Stmt::AugAssign(stmt_aug_assign) => self.visit_aug_assign(session, stmt_aug_assign), + Stmt::TypeAlias(stmt_type_alias) => self.visit_type_alias(session, stmt_type_alias), + Stmt::With(stmt_with) => self.visit_with(session, stmt_with), + Stmt::Match(stmt_match) => self.visit_match(session, stmt_match), + Stmt::Raise(stmt_raise) => self.visit_raise(session, stmt_raise), + Stmt::Assert(stmt_assert) => self.visit_assert(session, stmt_assert), + Stmt::Global(_) => {}, + Stmt::Nonlocal(_) => {}, + Stmt::Pass(_) => {}, + Stmt::IpyEscapeCommand(_) => {}, + } + }; + } + + fn _resolve_import(&mut self, session: &mut SessionInfo, _from_stmt: Option<&Identifier>, name_aliases: &[Alias], _level: Option, _range: &TextRange) { + let file_symbol = self.sym_stack[0].borrow().get_file(); + let file_symbol = file_symbol.expect("file symbol not found").upgrade().expect("unable to upgrade file symbol"); + let Some(eval_search) = session.sync_odoo.evaluation_search.clone() else { + return; + }; + for alias in name_aliases.iter() { + if alias.name.id == "*" { + continue; + } + let var_name = if alias.asname.is_none() { + S!(alias.name.split(".").next().unwrap()) + } else { + alias.asname.as_ref().unwrap().clone().to_string() + }; + let variable = self.sym_stack.last().unwrap().borrow().get_positioned_symbol(&OYarn::from(var_name), &alias.range); + if let Some(variable) = variable { + for evaluation in variable.borrow().evaluations().as_ref().unwrap().iter() { + let eval_sym = evaluation.symbol.get_symbol(session, &mut None, &mut vec![], Some(file_symbol.clone())); + match eval_sym { + EvaluationSymbolPtr::WEAK(w) => { + if let Some(symbol) = w.weak.upgrade() { + if Rc::ptr_eq(&symbol, &eval_search) { + let range = session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, &file_symbol.borrow().paths()[0], &alias.range); + session.sync_odoo.evaluation_locations.push(Location { + uri: FileMgr::pathname2uri(&file_symbol.borrow().paths()[0]), + range: range, + }); + } + } + }, + _ => { + panic!("Internal error: evaluated has invalid evaluationType"); + } + } + } + } + } + } + + fn visit_class_def(&mut self, session: &mut SessionInfo, c: &StmtClassDef) { + let sym = self.sym_stack.last().unwrap().borrow().get_positioned_symbol(&OYarn::from(c.name.to_string()), &c.range); + if let Some(sym) = sym { + self.sym_stack.push(sym); + self.visit_vec_stmt(session, &c.body); + self.sym_stack.pop(); + } + } + + fn visit_expr(&mut self, session: &mut SessionInfo, expr: &Expr, max_infer: &TextSize) { + Evaluation::eval_from_ast(session, expr, self.sym_stack.last().unwrap().clone(), max_infer, false, &mut vec![]); + } + + fn visit_assert(&mut self, session: &mut SessionInfo, stmt_assert: &StmtAssert) { + self.visit_expr(session, &stmt_assert.test, &stmt_assert.range.start()); + if let Some(msg) = stmt_assert.msg.as_ref() { + self.visit_expr(session, msg, &stmt_assert.range.start()); + } + } + + fn visit_raise(&mut self, session: &mut SessionInfo, stmt_raise: &StmtRaise) { + if let Some(exc) = stmt_raise.exc.as_ref() { + self.visit_expr(session, exc, &stmt_raise.range.start()); + } + } + + fn visit_match(&mut self, session: &mut SessionInfo, stmt_match: &StmtMatch) { + self.visit_expr(session, &stmt_match.subject, &stmt_match.range.start()); + for case in stmt_match.cases.iter() { + if let Some(guard) = case.guard.as_ref() { + self.visit_expr(session, guard, &case.pattern.start()); + } + self.visit_vec_stmt(session, &case.body); + } + } + + fn visit_type_alias(&mut self, session: &mut SessionInfo, stmt_type_alias: &StmtTypeAlias) { + self.visit_expr(session, &stmt_type_alias.value, &stmt_type_alias.range.start()); + } + + fn visit_aug_assign(&mut self, session: &mut SessionInfo, assign: &StmtAugAssign) { + self.visit_expr(session, &assign.value, &assign.range.start()); + } + + fn visit_return_stmt(&mut self, session: &mut SessionInfo, stmt_return: &StmtReturn) { + if let Some(value) = stmt_return.value.as_ref() { + self.visit_expr(session, value, &stmt_return.range.start()); + } + } + + fn visit_if(&mut self, session: &mut SessionInfo, node: &StmtIf) { + self.visit_expr(session, &node.test, &node.test.start()); + self.visit_vec_stmt(session, &node.body); + for elses in node.elif_else_clauses.iter() { + if let Some(test) = &elses.test { + self.visit_expr(session, test, &test.start()); + } + self.visit_vec_stmt(session, &elses.body); + } + } + + fn visit_with(&mut self, session: &mut SessionInfo, stmt_with: &StmtWith) { + for item in stmt_with.items.iter() { + self.visit_expr(session, &item.context_expr, &stmt_with.range.start()); + } + self.visit_vec_stmt(session, &stmt_with.body); + } + + fn visit_assign(&mut self, session: &mut SessionInfo, assign: &StmtAssign) { + self.visit_expr(session, &assign.value, &assign.range.start()); + } + + fn visit_ann_assign(&mut self, session: &mut SessionInfo, assign: &StmtAnnAssign) { + if let Some(value) = assign.value.as_ref() { + self.visit_expr(session, value, &assign.range.start()); + } + } + + fn visit_try(&mut self, session: &mut SessionInfo, node: &StmtTry) { + //TODO handle handlers of try + self.visit_vec_stmt(session, &node.body); + } } \ No newline at end of file diff --git a/server/src/server.rs b/server/src/server.rs index 15a7cb04..3d374273 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -2,7 +2,7 @@ use std::{io::Error, panic, sync::{Arc, Mutex, atomic::AtomicBool}, thread::Join use crossbeam_channel::{Receiver, Select, Sender}; use lsp_server::{Connection, IoThreads, Message, ProtocolError, RequestId, ResponseError}; -use lsp_types::{CancelParams, CompletionOptions, DefinitionOptions, DocumentSymbolOptions, FileOperationFilter, FileOperationPattern, FileOperationRegistrationOptions, HoverProviderCapability, InitializeParams, InitializeResult, OneOf, ReferencesOptions, SaveOptions, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, WorkDoneProgressOptions, WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, WorkspaceSymbolOptions, notification::{Cancel, DidChangeConfiguration, DidChangeTextDocument, DidChangeWatchedFiles, DidChangeWorkspaceFolders, DidCloseTextDocument, DidCreateFiles, DidDeleteFiles, DidOpenTextDocument, DidRenameFiles, DidSaveTextDocument, Notification}, request::{Completion, DocumentSymbolRequest, GotoDefinition, HoverRequest, References, Request, ResolveCompletionItem, Shutdown, WorkspaceSymbolRequest, WorkspaceSymbolResolve}}; +use lsp_types::{CancelParams, CompletionOptions, DeclarationOptions, DefinitionOptions, DocumentSymbolOptions, FileOperationFilter, FileOperationPattern, FileOperationRegistrationOptions, HoverProviderCapability, InitializeParams, InitializeResult, OneOf, ReferencesOptions, SaveOptions, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, WorkDoneProgressOptions, WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, WorkspaceSymbolOptions, notification::{Cancel, DidChangeConfiguration, DidChangeTextDocument, DidChangeWatchedFiles, DidChangeWorkspaceFolders, DidCloseTextDocument, DidCreateFiles, DidDeleteFiles, DidOpenTextDocument, DidRenameFiles, DidSaveTextDocument, Notification}, request::{Completion, DocumentSymbolRequest, GotoDeclaration, GotoDefinition, HoverRequest, References, Request, ResolveCompletionItem, Shutdown, WorkspaceSymbolRequest, WorkspaceSymbolResolve}}; use ruff_source_file::PositionEncoding; use serde_json::json; #[cfg(target_os = "linux")] @@ -159,6 +159,13 @@ impl Server { save: Some(lsp_types::TextDocumentSyncSaveOptions::SaveOptions(SaveOptions{include_text: Some(false)})) //TODO could deactivate if set on 'afterDelay? })), hover_provider: Some(HoverProviderCapability::Simple(true)), + declaration_provider: Some(lsp_types::DeclarationCapability::Options( + DeclarationOptions { + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: Some(false) + } + } + )), definition_provider: Some(OneOf::Right(DefinitionOptions{ work_done_progress_options: WorkDoneProgressOptions{ work_done_progress: Some(false) @@ -172,7 +179,7 @@ impl Server { references_provider: Some(OneOf::Right(ReferencesOptions { work_done_progress_options: WorkDoneProgressOptions { work_done_progress: Some(false) - } + }, })), document_symbol_provider: Some(OneOf::Right(DocumentSymbolOptions{ label: Some(S!("Odoo")), @@ -382,7 +389,7 @@ impl Server { Message::Request(r) => { self.running_request_ids.lock().unwrap().push(r.id.clone()); match r.method.as_str() { - HoverRequest::METHOD | GotoDefinition::METHOD | References::METHOD | DocumentSymbolRequest::METHOD | + HoverRequest::METHOD | GotoDefinition::METHOD | GotoDeclaration::METHOD | References::METHOD | DocumentSymbolRequest::METHOD | WorkspaceSymbolRequest::METHOD | WorkspaceSymbolResolve::METHOD | Completion::METHOD => { self.interrupt_rebuild_boolean.store(true, std::sync::atomic::Ordering::SeqCst); if DEBUG_THREADS { diff --git a/server/src/threads.rs b/server/src/threads.rs index e20afd08..b799c391 100644 --- a/server/src/threads.rs +++ b/server/src/threads.rs @@ -2,9 +2,9 @@ use std::{collections::VecDeque, path::PathBuf, sync::{Arc, Mutex}, time::Instan use crossbeam_channel::{Receiver, Sender, TryRecvError}; use lsp_server::{Message, RequestId, Response, ResponseError}; -use lsp_types::{CompletionResponse, DocumentSymbolResponse, Hover, Location, LogMessageParams, MessageType, ShowMessageParams, WorkspaceSymbol, WorkspaceSymbolResponse, notification::{DidChangeConfiguration, DidChangeTextDocument, DidChangeWatchedFiles, DidChangeWorkspaceFolders, +use lsp_types::{CompletionResponse, DocumentSymbolResponse, GotoDefinitionResponse, Hover, Location, LogMessageParams, MessageType, ShowMessageParams, WorkspaceSymbol, WorkspaceSymbolResponse, notification::{DidChangeConfiguration, DidChangeTextDocument, DidChangeWatchedFiles, DidChangeWorkspaceFolders, DidCloseTextDocument, DidCreateFiles, DidDeleteFiles, DidOpenTextDocument, DidRenameFiles, DidSaveTextDocument, LogMessage, - Notification, ShowMessage}, request::{Completion, DocumentSymbolRequest, GotoDefinition, GotoTypeDefinitionResponse, HoverRequest, References, Request, Shutdown, WorkspaceSymbolRequest, WorkspaceSymbolResolve}}; + Notification, ShowMessage}, request::{Completion, DocumentSymbolRequest, GotoDeclaration, GotoDeclarationResponse, GotoDefinition, HoverRequest, References, Request, Shutdown, WorkspaceSymbolRequest, WorkspaceSymbolResolve}}; use serde::{de::DeserializeOwned, Serialize}; use serde_json::Value; use tracing::{error, info, warn}; @@ -354,7 +354,12 @@ pub fn message_processor_thread_main(sync_odoo: Arc>, generic_re GotoDefinition::METHOD => { let mut session = create_session!(sender, receiver, sync_odoo, delayed_process_sender); SyncOdoo::process_rebuilds(&mut session, true); - to_value::(Odoo::handle_goto_definition(&mut session, serde_json::from_value(r.params).unwrap())) + to_value::(Odoo::handle_goto_definition(&mut session, serde_json::from_value(r.params).unwrap())) + }, + GotoDeclaration::METHOD => { + let mut session = create_session!(sender, receiver, sync_odoo, delayed_process_sender); + SyncOdoo::process_rebuilds(&mut session, true); + to_value::(Odoo::handle_goto_declaration(&mut session, serde_json::from_value(r.params).unwrap())) }, References::METHOD => { let mut session = create_session!(sender, receiver, sync_odoo, delayed_process_sender); diff --git a/server/src/utils.rs b/server/src/utils.rs index 6e9e585b..fe032b50 100644 --- a/server/src/utils.rs +++ b/server/src/utils.rs @@ -374,3 +374,27 @@ macro_rules! warn_or_panic { } } } + +use std::hash::{BuildHasherDefault, Hasher}; +#[derive(Default)] +pub struct U32IdentityHasher(u64); + +impl Hasher for U32IdentityHasher { + fn write(&mut self, bytes: &[u8]) { + // fallback lent (rare si on n'utilise que u32) + let mut v = 0u64; + for (i, b) in bytes.iter().enumerate().take(8) { + v |= (*b as u64) << (i * 8); + } + self.0 = v; + } + + fn write_u32(&mut self, i: u32) { + self.0 = i as u64; + } + + fn finish(&self) -> u64 { + self.0 + } +} +pub type NoHashBuilder = BuildHasherDefault; \ No newline at end of file diff --git a/server/tests/data/addons/module_1/models/base_test_models.py b/server/tests/data/addons/module_1/models/base_test_models.py index 02d7c8fb..7a8a8777 100644 --- a/server/tests/data/addons/module_1/models/base_test_models.py +++ b/server/tests/data/addons/module_1/models/base_test_models.py @@ -46,4 +46,6 @@ def _get_partner_id(self): return_funcBase2 = baseInstance2.get_test_int() class NoBaseModel(models.Model): - _inherit = "module_1.no_base_model" \ No newline at end of file + _inherit = "module_1.no_base_model" + +basic_var = 42 \ No newline at end of file diff --git a/server/tests/data/addons/module_1/models/models.py b/server/tests/data/addons/module_1/models/models.py index c8a9625f..482aa9ef 100644 --- a/server/tests/data/addons/module_1/models/models.py +++ b/server/tests/data/addons/module_1/models/models.py @@ -1,4 +1,7 @@ from odoo import models, fields +from .base_test_models import basic_var + +print(basic_var) class model_model(models.Model): pass diff --git a/server/tests/test_get_symbol.rs b/server/tests/test_get_symbol.rs index 653cd847..c85450b1 100644 --- a/server/tests/test_get_symbol.rs +++ b/server/tests/test_get_symbol.rs @@ -254,6 +254,7 @@ fn test_definition() { // check that one of the phone_code_locs is the same as the phone_code field assert!(phone_code_locs.iter().any(|loc| loc.target_range == file_mgr.borrow().text_range_to_range(&mut session, &phone_code_file, phone_code_field_sym.borrow().range())), "Expected phone_code to be at the same location as the field"); } + #[test] fn test_model_subscription() { // Setup: Get the symbol for BaseTestModel and verify its existence diff --git a/server/tests/test_references.rs b/server/tests/test_references.rs new file mode 100644 index 00000000..598db5b2 --- /dev/null +++ b/server/tests/test_references.rs @@ -0,0 +1,95 @@ +use std::path::PathBuf; + +use lsp_types::{Location, PartialResultParams, Position, WorkDoneProgressParams}; +use odoo_ls_server::{core::file_mgr::FileMgr, threads::SessionInfo, utils::PathSanitizer}; + +use crate::setup::setup::{create_init_session, setup_server}; + +mod setup; + +#[test] +/// Test various calls to GotoReferences +fn test_references() { + // Setup server and session + let (mut odoo, config) = setup_server(true); + let mut session = create_init_session(&mut odoo, config); + let test_addons_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests").join("data").join("addons"); + + let test_file = test_addons_path.join("module_1").join("models").join("base_test_models.py").sanitize(); + //1. reference of a model + let mut references = get_references(&mut session, &test_file, Position::new(4, 29)); + //as order is not guaranteed (usage of set), check if the expected reference is somewhere in the result list. + assert_in_result(&mut references, "module_1/models/base_test_models.py", 4, 12); + assert_in_result(&mut references, "module_1/models/base_test_models.py", 15, 8); + assert_in_result(&mut references, "module_1/models/base_test_models.py", 32, 17); + assert_in_result(&mut references, "module_1/models/base_test_models.py", 33, 8); + assert_in_result(&mut references, "module_1/models/base_test_models.py", 34, 18); + assert_in_result(&mut references, "module_1/models/base_test_models.py", 40, 16); + assert_in_result(&mut references, "module_1/models/base_test_models.py", 41, 16); + assert_in_result(&mut references, "module_1/models/base_test_models.py", 42, 16); + assert_in_result(&mut references, "module_1/models/base_test_models.py", 43, 16); + assert_in_result(&mut references, "module_1/models/diagnostics.py", 9, 34); + assert_in_result(&mut references, "module_1/models/diagnostics.py", 17, 34); + assert_in_result(&mut references, "module_1/models/diagnostics.py", 18, 34); + assert_in_result(&mut references, "module_1/models/diagnostics.py", 19, 34); + assert_in_result(&mut references, "module_1/models/diagnostics.py", 20, 34); + assert_in_result(&mut references, "module_1/models/diagnostics.py", 22, 34); + assert_in_result(&mut references, "module_2/models/base_test_models.py", 5, 15); + assert!(references.len() == 0, "Some references were not expected: {}", + references.iter().map(|r| format!("{}:{}:{}", r.uri.as_str(), r.range.start.line, r.range.start.character)).collect::>().join(", ") + ); + + // reference of an attribute + let mut references = get_references(&mut session, &test_file, Position::new(9, 8)); + assert_in_result(&mut references, "module_1/models/base_test_models.py", 37, 18); + assert!(references.len() == 0, "Some references were not expected: {}", + references.iter().map(|r| format!("{}:{}:{}", r.uri.as_str(), r.range.start.line, r.range.start.character)).collect::>().join(", ") + ); + + //reference of a simple variable + let mut references = get_references(&mut session, &test_file, Position::new(50, 4)); + assert_in_result(&mut references, "module_1/models/models.py", 1, 30); + assert!(references.len() == 0, "Some references were not expected: {}", + references.iter().map(|r| format!("{}:{}:{}", r.uri.as_str(), r.range.start.line, r.range.start.character)).collect::>().join(", ") + ); + + // for r in references.iter() { + // error!("Reference found at {}:{}:{}", r.uri.as_str(), r.range.start.line, r.range.start.character); + // } +} + +fn assert_in_result(references: &mut Vec, end_path: &str, line: u32, character: u32) { + let mut index = None; + for (i, r) in references.iter().enumerate() { + if r.uri.as_str().ends_with(end_path) && r.range.start.line == line && r.range.start.character == character { + index = Some(i); + } + } + if let Some(i) = index { + references.remove(i); + } else { + assert!(false, "Expected reference not found: {}:{}:{}", end_path, line, character); + } +} + +fn get_references(session: &mut SessionInfo, path: &String, position: Position)-> Vec { + let test_file_uri = FileMgr::pathname2uri(&path); + let references_params = lsp_types::ReferenceParams { + text_document_position: lsp_types::TextDocumentPositionParams { + text_document: lsp_types::TextDocumentIdentifier { + uri: test_file_uri, + }, + position, + }, + context: lsp_types::ReferenceContext { + include_declaration: true, + }, + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }; + let references = odoo_ls_server::core::odoo::Odoo::handle_references(session, references_params); + assert!(references.is_ok(), "Expected Some result from handle_references, got Err"); + let references = references.unwrap(); + assert!(references.is_some(), "Expected Some result from handle_references, got None for {}:{}-{}", path, position.line, position.character); + references.unwrap() +} diff --git a/server/tests/test_utils.rs b/server/tests/test_utils.rs index 1078a877..fc25bc4b 100644 --- a/server/tests/test_utils.rs +++ b/server/tests/test_utils.rs @@ -169,3 +169,4 @@ pub fn get_resolved_symbols_at_position( .filter_map(|ev| ev.upgrade_weak()) .collect() } +