Skip to content

Commit 97ce1ef

Browse files
committed
[IMP] server: hover and gotodefinition for record, fields in XML
1 parent 712415f commit 97ce1ef

File tree

11 files changed

+324
-55
lines changed

11 files changed

+324
-55
lines changed

server/src/core/entry_point.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,10 @@ impl EntryPoint {
349349
self.typ == EntryPointType::PUBLIC || self.typ == EntryPointType::BUILTIN
350350
}
351351

352+
pub fn is_main(&self) -> bool {
353+
self.typ == EntryPointType::MAIN || self.typ == EntryPointType::ADDON
354+
}
355+
352356
pub fn get_symbol(&self) -> Option<Rc<RefCell<Symbol>>> {
353357
let tree = self.addon_to_odoo_tree.as_ref().unwrap_or(&self.tree).clone();
354358
let symbol = self.root.borrow().get_symbol(&(tree, vec![]), u32::MAX);

server/src/core/file_mgr.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ pub fn combine_noqa_info(noqas: &Vec<NoqaInfo>) -> NoqaInfo {
4444
NoqaInfo::Codes(codes.iter().cloned().collect())
4545
}
4646

47-
#[derive(Debug)]
47+
#[derive(Debug, Clone)]
4848
pub enum AstType {
4949
Python,
5050
Xml,
@@ -351,6 +351,13 @@ impl FileInfo {
351351
}
352352
}
353353

354+
pub fn std_range_to_range(&self, range: &std::ops::Range<usize>) -> Range {
355+
Range {
356+
start: self.offset_to_position(range.start),
357+
end: self.offset_to_position(range.end)
358+
}
359+
}
360+
354361
pub fn position_to_offset_with_rope(rope: &Rope, line: u32, char: u32) -> usize {
355362
let line_char = rope.try_line_to_char(line as usize).expect("unable to get char from line");
356363
rope.try_char_to_byte(line_char + char as usize).expect("unable to get byte from char")
@@ -423,6 +430,29 @@ impl FileMgr {
423430
};
424431
Range::default()
425432
}
433+
434+
435+
pub fn std_range_to_range(&self, session: &mut SessionInfo, path: &String, range: &std::ops::Range<usize>) -> Range {
436+
let file = self.files.get(path);
437+
if let Some(file) = file {
438+
if file.borrow().file_info_ast.borrow().text_rope.is_none() {
439+
file.borrow_mut().prepare_ast(session);
440+
}
441+
return file.borrow().std_range_to_range(range);
442+
}
443+
//file not in cache, let's load rope on the fly
444+
match fs::read_to_string(path) {
445+
Ok(content) => {
446+
let rope = ropey::Rope::from(content.as_str());
447+
return Range {
448+
start: FileInfo::offset_to_position_with_rope(&rope, range.start),
449+
end: FileInfo::offset_to_position_with_rope(&rope, range.end)
450+
};
451+
},
452+
Err(_) => session.log_message(MessageType::ERROR, format!("Failed to read file {}", path))
453+
};
454+
Range::default()
455+
}
426456

427457
pub fn update_file_info(&mut self, session: &mut SessionInfo, uri: &str, content: Option<&Vec<TextDocumentContentChangeEvent>>, version: Option<i32>, force: bool) -> (bool, Rc<RefCell<FileInfo>>) {
428458
let file_info = self.files.entry(uri.to_string()).or_insert_with(|| Rc::new(RefCell::new(FileInfo::new(uri.to_string()))));

server/src/core/odoo.rs

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
use crate::core::entry_point::EntryPointType;
2+
use crate::core::file_mgr::AstType;
3+
use crate::core::symbols::file_symbol;
4+
use crate::core::xml_data::XmlData;
25
use crate::core::xml_validation::XmlValidator;
36
use crate::features::document_symbols::DocumentSymbolFeature;
47
use crate::threads::SessionInfo;
@@ -930,6 +933,47 @@ impl SyncOdoo {
930933
self.capabilities = capabilities.clone();
931934
}
932935

936+
/**
937+
* search for an xml_id in the already registered xml files.
938+
* */
939+
pub fn get_xml_ids(&mut self, from_file: &Rc<RefCell<Symbol>>, xml_id: &str, range: &std::ops::Range<usize>, diagnostics: &mut Vec<Diagnostic>) -> Vec<XmlData> {
940+
if !from_file.borrow().get_entry().unwrap().borrow().is_main() {
941+
return vec![];
942+
}
943+
let id_split = xml_id.split(".").collect::<Vec<&str>>();
944+
let mut module = None;
945+
if id_split.len() == 1 {
946+
// If no module name, we are in the current module
947+
module = from_file.borrow().find_module();
948+
} else if id_split.len() == 2 {
949+
// Try to find the module by name
950+
if let Some(m) = self.modules.get(&Sy!(id_split.first().unwrap().to_string())) {
951+
module = m.upgrade();
952+
}
953+
} else if id_split.len() > 2 {
954+
diagnostics.push(Diagnostic::new(
955+
Range {
956+
start: Position::new(range.start as u32, 0),
957+
end: Position::new(range.end as u32, 0),
958+
},
959+
Some(DiagnosticSeverity::ERROR),
960+
Some(lsp_types::NumberOrString::String(S!("OLS30446"))),
961+
Some(EXTENSION_NAME.to_string()),
962+
format!("Invalid XML ID '{}'. It should not contain more than one dot", xml_id),
963+
None,
964+
None
965+
));
966+
return vec![];
967+
}
968+
if module.is_none() {
969+
warn!("Module not found for id: {}", xml_id);
970+
return vec![];
971+
}
972+
let module = module.unwrap();
973+
let module = module.borrow();
974+
module.as_module_package().get_xml_id(&oyarn!("{}", id_split.last().unwrap()))
975+
}
976+
933977
}
934978

935979
#[derive(Debug)]
@@ -1105,8 +1149,19 @@ impl Odoo {
11051149
if file_info.borrow().file_info_ast.borrow().ast.is_none() {
11061150
file_info.borrow_mut().prepare_ast(session);
11071151
}
1108-
if file_info.borrow_mut().file_info_ast.borrow().ast.is_some() {
1109-
return Ok(HoverFeature::get_hover(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character));
1152+
let ast_type = file_info.borrow().file_info_ast.borrow().ast_type.clone();
1153+
match ast_type {
1154+
AstType::Python => {
1155+
if file_info.borrow_mut().file_info_ast.borrow().ast.is_some() {
1156+
return Ok(HoverFeature::hover_python(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character));
1157+
}
1158+
},
1159+
AstType::Xml => {
1160+
return Ok(HoverFeature::hover_xml(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character));
1161+
},
1162+
AstType::Csv => {
1163+
return Ok(HoverFeature::hover_csv(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character));
1164+
},
11101165
}
11111166
}
11121167
}
@@ -1131,8 +1186,19 @@ impl Odoo {
11311186
if file_info.borrow().file_info_ast.borrow().ast.is_none() {
11321187
file_info.borrow_mut().prepare_ast(session);
11331188
}
1134-
if file_info.borrow().file_info_ast.borrow().ast.is_some() {
1135-
return Ok(DefinitionFeature::get_location(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character));
1189+
let ast_type = file_info.borrow().file_info_ast.borrow().ast_type.clone();
1190+
match ast_type {
1191+
AstType::Python => {
1192+
if file_info.borrow().file_info_ast.borrow().ast.is_some() {
1193+
return Ok(DefinitionFeature::get_location(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character));
1194+
}
1195+
},
1196+
AstType::Xml => {
1197+
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));
1198+
},
1199+
AstType::Csv => {
1200+
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));
1201+
},
11361202
}
11371203
}
11381204
}

server/src/core/xml_arch_builder.rs

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -86,49 +86,8 @@ impl XmlArchBuilder {
8686
}
8787
}
8888

89-
/**
90-
* search for an xml_id in the already registered xml files.
91-
* */
92-
pub fn get_xml_ids(&self, session: &mut SessionInfo, xml_id: &str, attr: &Attribute, diagnostics: &mut Vec<Diagnostic>) -> Vec<XmlData> {
93-
if !self.is_in_main_ep {
94-
return vec![];
95-
}
96-
let id_split = xml_id.split(".").collect::<Vec<&str>>();
97-
let mut module = None;
98-
if id_split.len() == 1 {
99-
// If no module name, we are in the current module
100-
module = self.xml_symbol.borrow().find_module();
101-
} else if id_split.len() == 2 {
102-
// Try to find the module by name
103-
if let Some(m) = session.sync_odoo.modules.get(&Sy!(id_split.first().unwrap().to_string())) {
104-
module = m.upgrade();
105-
}
106-
} else if id_split.len() > 2 {
107-
diagnostics.push(Diagnostic::new(
108-
Range {
109-
start: Position::new(attr.range().start as u32, 0),
110-
end: Position::new(attr.range().end as u32, 0),
111-
},
112-
Some(DiagnosticSeverity::ERROR),
113-
Some(lsp_types::NumberOrString::String(S!("OLS30446"))),
114-
Some(EXTENSION_NAME.to_string()),
115-
format!("Invalid XML ID '{}'. It should not contain more than one dot", xml_id),
116-
None,
117-
None
118-
));
119-
return vec![];
120-
}
121-
if module.is_none() {
122-
warn!("Module not found for id: {}", xml_id);
123-
return vec![];
124-
}
125-
let module = module.unwrap();
126-
let module = module.borrow();
127-
module.as_module_package().get_xml_id(&oyarn!("{}", id_split.last().unwrap()))
128-
}
129-
13089
pub fn get_group_ids(&self, session: &mut SessionInfo, xml_id: &str, attr: &Attribute, diagnostics: &mut Vec<Diagnostic>) -> Vec<XmlData> {
131-
let xml_ids = self.get_xml_ids(session, xml_id, attr, diagnostics);
90+
let xml_ids = session.sync_odoo.get_xml_ids(&self.xml_symbol, xml_id, &attr.range(), diagnostics);
13291
let mut res = vec![];
13392
for data in xml_ids.iter() {
13493
match data {

server/src/core/xml_arch_builder_rng_validation.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ impl XmlArchBuilder {
105105
}
106106
}
107107
//check that action exists
108-
if self.get_xml_ids(session, attr.value(), &attr, diagnostics).is_empty() {
108+
if session.sync_odoo.get_xml_ids(&self.xml_symbol, attr.value(), &attr.range(), diagnostics).is_empty() {
109109
if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05053, &[attr.value()]) {
110110
diagnostics.push(Diagnostic {
111111
range: Range { start: Position::new(attr.range().start as u32, 0), end: Position::new(attr.range().end as u32, 0) },
@@ -124,7 +124,7 @@ impl XmlArchBuilder {
124124
}
125125
} else {
126126
//check that parent exists
127-
if self.get_xml_ids(session, attr.value(), &attr, diagnostics).is_empty() {
127+
if session.sync_odoo.get_xml_ids(&self.xml_symbol, attr.value(), &attr.range(), diagnostics).is_empty() {
128128
if let Some(diagnostic) = create_diagnostic(session, DiagnosticCode::OLS05052, &[attr.value()]) {
129129
diagnostics.push(Diagnostic {
130130
range: Range { start: Position::new(attr.range().start as u32, 0), end: Position::new(attr.range().end as u32, 0) },

server/src/core/xml_validation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ impl XmlValidator {
112112
for field in &xml_data_record.fields {
113113
let declared_field = all_fields.get(&field.name);
114114
if let Some(declared_field) = declared_field {
115-
mandatory_fields.retain(|f| f != &field.name);
115+
mandatory_fields.retain(|f| f != &field.name.as_str());
116116
//TODO Check type
117117
} else {
118118

server/src/features/definition.rs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use lsp_types::{GotoDefinitionResponse, Location, Range};
1+
use lsp_types::{GotoDefinitionResponse, Location, Position, Range};
22
use ruff_python_ast::{Expr, ExprCall};
33
use ruff_text_size::TextSize;
44
use std::path::PathBuf;
@@ -8,8 +8,10 @@ use crate::constants::SymType;
88
use crate::core::evaluation::{Evaluation, EvaluationValue};
99
use crate::core::file_mgr::{FileInfo, FileMgr};
1010
use crate::core::symbols::symbol::Symbol;
11+
use crate::core::symbols::xml_file_symbol;
1112
use crate::features::ast_utils::AstUtils;
1213
use crate::features::features_utils::FeaturesUtils;
14+
use crate::features::xml_ast_utils::{XmlAstResult, XmlAstUtils};
1315
use crate::oyarn;
1416
use crate::threads::SessionInfo;
1517
use crate::utils::PathSanitizer as _;
@@ -148,4 +150,66 @@ impl DefinitionFeature {
148150
Some(GotoDefinitionResponse::Array(links))
149151
}
150152

153+
pub fn get_location_xml(session: &mut SessionInfo,
154+
file_symbol: &Rc<RefCell<Symbol>>,
155+
file_info: &Rc<RefCell<FileInfo>>,
156+
line: u32,
157+
character: u32
158+
) -> Option<GotoDefinitionResponse> {
159+
let offset = file_info.borrow().position_to_offset(line, character);
160+
let data = file_info.borrow().file_info_ast.borrow().text_rope.as_ref().unwrap().to_string();
161+
let document = roxmltree::Document::parse(&data);
162+
if let Ok(document) = document {
163+
let root = document.root_element();
164+
let (symbols, _range) = XmlAstUtils::get_symbols(session, file_symbol, root, offset);
165+
if symbols.is_empty() {
166+
return None;
167+
}
168+
let mut links = vec![];
169+
for xml_result in symbols.iter() {
170+
match xml_result {
171+
crate::features::xml_ast_utils::XmlAstResult::SYMBOL(s) => {
172+
if let Some(file) = s.borrow().get_file() {
173+
for path in file.upgrade().unwrap().borrow().paths().iter() {
174+
let full_path = match file.upgrade().unwrap().borrow().typ() {
175+
SymType::PACKAGE(_) => PathBuf::from(path).join(format!("__init__.py{}", file.upgrade().unwrap().borrow().as_package().i_ext())).sanitize(),
176+
_ => path.clone()
177+
};
178+
let range = match s.borrow().typ() {
179+
SymType::PACKAGE(_) | SymType::FILE | SymType::NAMESPACE | SymType::DISK_DIR => Range::default(),
180+
_ => session.sync_odoo.get_file_mgr().borrow().text_range_to_range(session, &full_path, &s.borrow().range()),
181+
};
182+
links.push(Location{uri: FileMgr::pathname2uri(&full_path), range});
183+
}
184+
}
185+
},
186+
XmlAstResult::XML_DATA(xml_file_symbol, range) => {
187+
for path in xml_file_symbol.borrow().paths().iter() {
188+
let full_path = match xml_file_symbol.borrow().typ() {
189+
SymType::PACKAGE(_) => PathBuf::from(path).join(format!("__init__.py{}", xml_file_symbol.borrow().as_package().i_ext())).sanitize(),
190+
_ => path.clone()
191+
};
192+
let range = match xml_file_symbol.borrow().typ() {
193+
SymType::PACKAGE(_) | SymType::FILE | SymType::NAMESPACE | SymType::DISK_DIR => Range::default(),
194+
_ => session.sync_odoo.get_file_mgr().borrow().std_range_to_range(session, &full_path, &range),
195+
};
196+
links.push(Location{uri: FileMgr::pathname2uri(&full_path), range: range});
197+
}
198+
}
199+
}
200+
}
201+
return Some(GotoDefinitionResponse::Array(links));
202+
}
203+
None
204+
}
205+
206+
pub fn get_location_csv(session: &mut SessionInfo,
207+
file_symbol: &Rc<RefCell<Symbol>>,
208+
file_info: &Rc<RefCell<FileInfo>>,
209+
line: u32,
210+
character: u32
211+
) -> Option<GotoDefinitionResponse> {
212+
None
213+
}
214+
151215
}

server/src/features/hover.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
use lsp_types::{Hover, HoverContents, MarkupContent, Range};
1+
use lsp_types::{Hover, HoverContents, MarkupContent, Position, Range};
2+
use crate::core::evaluation::Evaluation;
23
use crate::core::file_mgr::FileInfo;
4+
use crate::features::xml_ast_utils::{XmlAstResult, XmlAstUtils};
35
use crate::threads::SessionInfo;
46
use std::rc::Rc;
57
use crate::core::symbols::symbol::Symbol;
@@ -12,7 +14,7 @@ pub struct HoverFeature {}
1214

1315
impl HoverFeature {
1416

15-
pub fn get_hover(session: &mut SessionInfo, file_symbol: &Rc<RefCell<Symbol>>, file_info: &Rc<RefCell<FileInfo>>, line: u32, character: u32) -> Option<Hover> {
17+
pub fn hover_python(session: &mut SessionInfo, file_symbol: &Rc<RefCell<Symbol>>, file_info: &Rc<RefCell<FileInfo>>, line: u32, character: u32) -> Option<Hover> {
1618
let offset = file_info.borrow().position_to_offset(line, character);
1719
let (analyse_ast_result, range, call_expr) = AstUtils::get_symbols(session, file_symbol, file_info, offset as u32);
1820
let evals = analyse_ast_result.evaluations;
@@ -28,4 +30,29 @@ impl HoverFeature {
2830
range: range
2931
})
3032
}
33+
34+
pub fn hover_xml(session: &mut SessionInfo, file_symbol: &Rc<RefCell<Symbol>>, file_info: &Rc<RefCell<FileInfo>>, line: u32, character: u32) -> Option<Hover> {
35+
let offset = file_info.borrow().position_to_offset(line, character);
36+
let data = file_info.borrow().file_info_ast.borrow().text_rope.as_ref().unwrap().to_string();
37+
let document = roxmltree::Document::parse(&data);
38+
if let Ok(document) = document {
39+
let root = document.root_element();
40+
let (symbols, range) = XmlAstUtils::get_symbols(session, file_symbol, root, offset);
41+
let range = range.map(|r| (file_info.borrow().std_range_to_range(&r)));
42+
let evals = symbols.iter().filter(|s| matches!(s, XmlAstResult::SYMBOL(_)))
43+
.map(|s| Evaluation::eval_from_symbol(&Rc::downgrade(&s.as_symbol()), Some(false))).collect::<Vec<Evaluation>>();
44+
return Some(Hover { contents:
45+
HoverContents::Markup(MarkupContent {
46+
kind: lsp_types::MarkupKind::Markdown,
47+
value: FeaturesUtils::build_markdown_description(session, Some(file_symbol.clone()), &evals, &None, Some(offset))
48+
}),
49+
range: range
50+
})
51+
}
52+
None
53+
}
54+
55+
pub fn hover_csv(session: &mut SessionInfo, file_symbol: &Rc<RefCell<Symbol>>, file_info: &Rc<RefCell<FileInfo>>, line: u32, character: u32) -> Option<Hover> {
56+
None
57+
}
3158
}

server/src/features/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ pub mod definition;
33
pub mod document_symbols;
44
pub mod hover;
55
pub mod ast_utils;
6-
pub mod features_utils;
6+
pub mod features_utils;
7+
pub mod xml_ast_utils;

0 commit comments

Comments
 (0)