Skip to content

Commit 7336cc3

Browse files
committed
[IMP] Add XML model records to models
This adds xml models and fields in the model struct. It is also handled in hover, definition, ... . Also includes tests
1 parent e55ed15 commit 7336cc3

16 files changed

Lines changed: 957 additions & 148 deletions

server/src/core/model.rs

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ use std::rc::Rc;
55
use std::rc::Weak;
66
use lsp_types::MessageType;
77
use weak_table::PtrWeakHashSet;
8+
use weak_table::PtrWeakKeyHashMap;
89
use std::collections::HashSet;
910

1011
use crate::constants::BuildStatus;
1112
use crate::constants::BuildSteps;
1213
use crate::constants::OYarn;
1314
use crate::constants::SymType;
15+
use crate::core::xml_data::OdooDataRecord;
1416
use crate::threads::SessionInfo;
1517

1618
use super::symbols::module_symbol::ModuleSymbol;
@@ -73,6 +75,10 @@ impl ModelData {
7375
pub struct Model {
7476
name: OYarn,
7577
symbols: PtrWeakHashSet<Weak<RefCell<Symbol>>>,
78+
// One XML file can have multiple records that define models or fields
79+
// xml symbols and model fields are thus maps from xml file symbol to a list of records
80+
xml_symbols: PtrWeakKeyHashMap<Weak<RefCell<Symbol>>, HashSet<OdooDataRecord>>,
81+
model_fields: PtrWeakKeyHashMap<Weak<RefCell<Symbol>>, HashSet<OdooDataRecord>>,
7682
pub dependents: PtrWeakHashSet<Weak<RefCell<Symbol>>>,
7783
}
7884

@@ -81,12 +87,30 @@ impl Model {
8187
let mut res = Self {
8288
name,
8389
symbols: PtrWeakHashSet::new(),
90+
xml_symbols: PtrWeakKeyHashMap::new(),
91+
model_fields: PtrWeakKeyHashMap::new(),
8492
dependents: PtrWeakHashSet::new(),
8593
};
8694
res.symbols.insert(symbol);
8795
res
8896
}
8997

98+
pub fn new_from_xml(name: OYarn, xml_file_symbol: Rc<RefCell<Symbol>>, record: OdooDataRecord) -> Self {
99+
let mut res = Self {
100+
name,
101+
symbols: PtrWeakHashSet::new(),
102+
xml_symbols: PtrWeakKeyHashMap::new(),
103+
model_fields: PtrWeakKeyHashMap::new(),
104+
dependents: PtrWeakHashSet::new(),
105+
};
106+
res.xml_symbols.insert(xml_file_symbol, HashSet::from([record]));
107+
res
108+
}
109+
110+
pub fn name(&self) -> &OYarn {
111+
&self.name
112+
}
113+
90114
pub fn add_symbol(&mut self, session: &mut SessionInfo, symbol: Rc<RefCell<Symbol>>) {
91115
if self.symbols.contains(&symbol) {
92116
return;
@@ -96,6 +120,78 @@ impl Model {
96120
self.add_dependents_to_validation(session, from_module);
97121
}
98122

123+
fn add_xml_ref(collection: &mut PtrWeakKeyHashMap<Weak<RefCell<Symbol>>, HashSet<OdooDataRecord>>, symbol: &Rc<RefCell<Symbol>>, record: OdooDataRecord) -> bool {
124+
if let Some(records) = collection.get_mut(symbol) {
125+
records.insert(record)
126+
} else {
127+
collection.insert(symbol.clone(), HashSet::from([record]));
128+
true
129+
}
130+
}
131+
132+
pub fn add_xml_symbol(&mut self, session: &mut SessionInfo, symbol: Rc<RefCell<Symbol>>, record: OdooDataRecord) {
133+
if Self::add_xml_ref(&mut self.xml_symbols, &symbol, record) {
134+
self.add_dependents_to_validation(session, symbol.borrow().find_module());
135+
}
136+
}
137+
138+
pub fn add_model_field(&mut self, session: &mut SessionInfo, symbol: Rc<RefCell<Symbol>>, record: OdooDataRecord) {
139+
if Self::add_xml_ref(&mut self.model_fields, &symbol, record) {
140+
self.add_dependents_to_validation(session, symbol.borrow().find_module());
141+
}
142+
}
143+
144+
145+
pub fn get_model_field_records(&self, session: &mut SessionInfo, from_module: Option<Rc<RefCell<Symbol>>>) -> Vec<(Rc<RefCell<Symbol>>, OdooDataRecord)> {
146+
let mut result = Vec::new();
147+
for (s, records) in self.model_fields.iter() {
148+
let module = s.borrow().find_module();
149+
if let Some(module) = module {
150+
if from_module.is_none() || ModuleSymbol::is_in_deps(session, from_module.as_ref().unwrap(), &module.borrow().as_module_package().dir_name) {
151+
for r in records.iter() {
152+
result.push((s.clone(), r.clone()));
153+
}
154+
}
155+
}
156+
}
157+
result
158+
}
159+
160+
161+
pub fn get_model_field_record_by_name(&self, session: &mut SessionInfo, from_module: Option<Rc<RefCell<Symbol>>>, field_name: &str) -> Vec<(Rc<RefCell<Symbol>>, OdooDataRecord)> {
162+
self.get_model_field_records(session, from_module).into_iter().filter(|(_, r)| {
163+
r.fields.iter().any(|f| f.name.as_str() == "name" && f.text.as_deref() == Some(field_name))
164+
}).collect()
165+
}
166+
167+
pub fn get_xml_symbols(&self, session: &mut SessionInfo, from_module: Option<Rc<RefCell<Symbol>>>) -> Vec<Rc<RefCell<Symbol>>> {
168+
let mut result = Vec::new();
169+
for (s, _records) in self.xml_symbols.iter() {
170+
let module = s.borrow().find_module();
171+
if let Some(module) = module {
172+
if from_module.is_none() || ModuleSymbol::is_in_deps(session, from_module.as_ref().unwrap(), &module.borrow().as_module_package().dir_name) {
173+
result.push(s);
174+
}
175+
}
176+
}
177+
result
178+
}
179+
180+
pub fn get_xml_symbol_records(&self, session: &mut SessionInfo, from_module: Option<Rc<RefCell<Symbol>>>) -> Vec<(Rc<RefCell<Symbol>>, OdooDataRecord)> {
181+
let mut result = Vec::new();
182+
for (s, record) in self.xml_symbols.iter() {
183+
let module = s.borrow().find_module();
184+
if let Some(module) = module {
185+
if from_module.is_none() || ModuleSymbol::is_in_deps(session, from_module.as_ref().unwrap(), &module.borrow().as_module_package().dir_name) {
186+
for r in record.iter() {
187+
result.push((s.clone(), r.clone()));
188+
}
189+
}
190+
}
191+
}
192+
result
193+
}
194+
99195
pub fn remove_symbol(&mut self, session: &mut SessionInfo, symbol: &Rc<RefCell<Symbol>>, from_module: Option<Rc<RefCell<Symbol>>>) {
100196
self.symbols.remove(symbol);
101197
self.add_dependents_to_validation(session, from_module);
@@ -130,13 +226,22 @@ impl Model {
130226
}
131227

132228
pub fn model_in_deps(&self, session: &mut SessionInfo, from_module: &Rc<RefCell<Symbol>>) -> bool {
133-
for sym in self.symbols.iter() {
134-
if !sym.borrow().as_class_sym()._model.as_ref().unwrap().inherit.contains(&sym.borrow().as_class_sym()._model.as_ref().unwrap().name) {
135-
let dir_name = sym.borrow().find_module().unwrap().borrow().as_module_package().dir_name.clone();
229+
let sym_module_check = |session: &mut SessionInfo<'_>, sym: &Rc<RefCell<Symbol>>| {
230+
if let Some(module) = sym.borrow().find_module() {
231+
let dir_name = module.borrow().as_module_package().dir_name.clone();
136232
if ModuleSymbol::is_in_deps(session, from_module, &dir_name) {
137233
return true;
138234
}
139235
}
236+
false
237+
};
238+
if self.symbols.iter().any(|sym|
239+
!sym.borrow().as_class_sym()._model.as_ref().unwrap().inherit.contains(&sym.borrow().as_class_sym()._model.as_ref().unwrap().name)
240+
&& sym_module_check(session, &sym)) {
241+
return true;
242+
}
243+
if self.xml_symbols.iter().any(|(sym, _record)| sym_module_check(session, &sym)) {
244+
return true;
140245
}
141246
false
142247
}
@@ -188,7 +293,9 @@ impl Model {
188293

189294
pub fn has_symbols(&mut self) -> bool {
190295
self.symbols.remove_expired();
191-
!self.symbols.is_empty()
296+
self.xml_symbols.remove_expired();
297+
self.model_fields.remove_expired();
298+
!self.symbols.is_empty() || !self.xml_symbols.is_empty()
192299
}
193300

194301
/* Return all symbols that build this model.

server/src/core/python_odoo_builder.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use weak_table::PtrWeakHashSet;
1010
use crate::constants::{OYarn, SymType};
1111
use crate::core::model::{Model, ModelData};
1212
use crate::core::symbols::symbol::Symbol;
13-
use crate::core::xml_data::{OdooData, OdooDataRecord};
13+
use crate::core::xml_data::{OdooData, OdooDataField, OdooDataRecord};
1414
use crate::threads::SessionInfo;
1515
use crate::utils::compare_semver;
1616
use crate::{oyarn, Sy, S};
@@ -72,7 +72,17 @@ impl PythonOdooBuilder {
7272
end: 1,
7373
}),
7474
xml_id: Some(xml_id_model_name),
75-
fields: vec![],
75+
fields: vec![
76+
OdooDataField {
77+
name: Sy!("model"),
78+
range: std::ops::Range::<usize> {
79+
start: self.symbol.borrow().range().start().to_usize(),
80+
end: self.symbol.borrow().range().end().to_usize(),
81+
},
82+
text: Some(model_name.to_string()),
83+
..Default::default()
84+
},
85+
],
7686
range: std::ops::Range::<usize> {
7787
start: self.symbol.borrow().range().start().to_usize(),
7888
end: self.symbol.borrow().range().end().to_usize(),

server/src/core/xml_arch_builder_rng_validation.rs

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use std::rc::Rc;
1+
use std::{cell::RefCell, rc::Rc};
22

33
use lsp_types::{Diagnostic, Position, Range};
44
use roxmltree::Node;
55

6-
use crate::{constants::OYarn, core::{diagnostics::{create_diagnostic, DiagnosticCode}, odoo::SyncOdoo, xml_data::{OdooData, XmlDataDelete, OdooDataField, XmlDataMenuItem, OdooDataRecord, XmlDataTemplate}}, oyarn, threads::SessionInfo, Sy};
6+
use crate::{constants::OYarn, core::{diagnostics::{create_diagnostic, DiagnosticCode}, model::Model, odoo::SyncOdoo, xml_data::{OdooData, XmlDataDelete, OdooDataField, XmlDataMenuItem, OdooDataRecord, XmlDataTemplate}}, oyarn, threads::SessionInfo, Sy};
77

88
use super::xml_arch_builder::XmlArchBuilder;
99

@@ -230,11 +230,97 @@ impl XmlArchBuilder {
230230
}
231231
}
232232
}
233+
self.register_ir_model_record(session, &data);
234+
self.register_ir_model_fields_record(session, &data);
233235
let data = OdooData::RECORD(data);
234236
self.on_operation_creation(session, found_id, node, data, diagnostics);
235237
true
236238
}
237239

240+
fn register_ir_model_fields_record(&self, session: &mut SessionInfo, data: &OdooDataRecord) {
241+
if data.model.0.as_str() != "ir.model.fields" {
242+
return;
243+
}
244+
245+
let model_name_str: String = if let Some(f) =
246+
data.fields.iter().find(|f| f.name.as_str() == "model")
247+
{
248+
let Some(t) = f.text.as_deref() else {
249+
return;
250+
};
251+
t.to_string()
252+
} else if let Some(f) = data.fields.iter().find(|f| f.name.as_str() == "model_id") {
253+
let Some((ref_key, ref_range)) = f.ref_key.as_ref() else {
254+
return;
255+
};
256+
let xml_ids =
257+
SyncOdoo::get_xml_ids(session, &self.xml_symbol, ref_key, ref_range, &mut vec![]);
258+
let model_name_option = xml_ids.iter().find_map(|xml_data| {
259+
let OdooData::RECORD(r) = xml_data else {
260+
return None;
261+
};
262+
if r.model.0.as_str() != "ir.model" {
263+
return None;
264+
}
265+
if let Some(name) = r
266+
.fields
267+
.iter()
268+
.find(|f| f.name.as_str() == "model")
269+
.and_then(|model_field| model_field.text.as_deref())
270+
{
271+
return Some(name.to_string());
272+
}
273+
None
274+
});
275+
match model_name_option {
276+
Some(model_name) => model_name,
277+
None => return,
278+
}
279+
} else {
280+
return;
281+
};
282+
let model_name_yarn = Sy!(model_name_str);
283+
let xml_sym = self.xml_symbol.clone();
284+
if let Some(model) = session.sync_odoo.models.get(&model_name_yarn).cloned() {
285+
model
286+
.borrow_mut()
287+
.add_model_field(session, xml_sym, data.clone());
288+
}
289+
}
290+
291+
fn register_ir_model_record(&self, session: &mut SessionInfo, data: &OdooDataRecord) {
292+
if data.model.0.as_str() != "ir.model" {
293+
return;
294+
}
295+
let Some(model_field) = data.fields.iter().find(|f| f.name.as_str() == "model") else {
296+
return;
297+
};
298+
let Some(model_name) = model_field.text.as_deref() else {
299+
return;
300+
};
301+
let model_name_yarn = oyarn!("{}", model_name);
302+
let xml_sym = self.xml_symbol.clone();
303+
match session.sync_odoo.models.get(&model_name_yarn).cloned() {
304+
Some(model) => {
305+
model
306+
.borrow_mut()
307+
.add_xml_symbol(session, xml_sym, data.clone());
308+
}
309+
None => {
310+
let new_model = Model::new_from_xml(model_name_yarn.clone(), xml_sym, data.clone());
311+
session
312+
.sync_odoo
313+
.models
314+
.insert(model_name_yarn.clone(), Rc::new(RefCell::new(new_model)));
315+
}
316+
}
317+
session
318+
.sync_odoo
319+
.get_main_entry()
320+
.borrow_mut()
321+
.search_rebuild_for_models(session, model_name_yarn);
322+
}
323+
238324
fn load_field(&mut self, session: &mut SessionInfo, node: &Node, diagnostics: &mut Vec<Diagnostic>) -> Option<OdooDataField> {
239325
if node.tag_name().name() != "field" { return None; }
240326
let Some(node_name_node) = node.attribute_node("name") else {

server/src/core/xml_data.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,29 @@ pub struct OdooDataRecord {
2020
pub range: Range<usize>,
2121
}
2222

23-
#[derive(Debug, Clone)]
23+
impl PartialEq for OdooDataRecord {
24+
fn eq(&self, other: &Self) -> bool {
25+
Weak::ptr_eq(&self.file_symbol, &other.file_symbol)
26+
&& self.model == other.model
27+
&& self.xml_id == other.xml_id
28+
&& self.fields == other.fields
29+
&& self.range == other.range
30+
}
31+
}
32+
33+
impl Eq for OdooDataRecord {}
34+
35+
impl std::hash::Hash for OdooDataRecord {
36+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
37+
self.file_symbol.as_ptr().hash(state);
38+
self.model.hash(state);
39+
self.xml_id.hash(state);
40+
self.fields.hash(state);
41+
self.range.hash(state);
42+
}
43+
}
44+
45+
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
2446
pub struct OdooDataField {
2547
pub name: OYarn,
2648
pub range: Range<usize>,

0 commit comments

Comments
 (0)