Skip to content

Commit 1e1d3ed

Browse files
committed
fix(python): field attributes
Closes #53
1 parent 71ad091 commit 1e1d3ed

File tree

10 files changed

+218
-176
lines changed

10 files changed

+218
-176
lines changed

justfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
test: (ensure_cargo "cargo-nextest")
2-
cargo nextest run -p odoo-lsp -p odoo-lsp-tests
1+
test *args: (ensure_cargo "cargo-nextest")
2+
cargo nextest run -p odoo-lsp -p odoo-lsp-tests {{args}}
33

44
bench: (ensure_cargo "iai-callgrind-runner")
55
cargo bench -p odoo-lsp-tests

src/analyze.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub enum FunctionParam {
8282
impl core::fmt::Display for FunctionParam {
8383
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
8484
match self {
85-
FunctionParam::Param(param) => f.write_str(&param),
85+
FunctionParam::Param(param) => f.write_str(param),
8686
FunctionParam::PosEnd => f.write_char('/'),
8787
FunctionParam::EitherEnd(None) => f.write_char('*'),
8888
FunctionParam::EitherEnd(Some(param)) => write!(f, "*{}", param),

src/python.rs

Lines changed: 31 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ mod tests;
2323

2424
#[rustfmt::skip]
2525
query! {
26-
PyCompletions(Request, XmlId, Mapped, MappedTarget, Depends, ReadFn, Model, Prop, ForXmlId, Scope, FieldDescriptor);
26+
PyCompletions(Request, XmlId, Mapped, MappedTarget, Depends, ReadFn, Model, Prop, ForXmlId, Scope, FieldDescriptor, FieldType);
2727

2828
(call [
2929
(attribute [
@@ -55,9 +55,11 @@ query! {
5555
(list ((string) @MODEL ","?)*)
5656
(call
5757
(attribute
58-
(identifier) @_fields (identifier) (#eq? @_fields "fields"))
58+
(identifier) @_fields (identifier) @FIELD_TYPE (#eq? @_fields "fields"))
5959
(argument_list
60-
. ((comment)* (string)? @MODEL)
60+
. [
61+
((comment)+ (string) @MODEL)
62+
(string) @MODEL ]?
6163
// handles `related` `compute` `search` and `inverse`
6264
((keyword_argument (identifier) @FIELD_DESCRIPTOR (_)) ","?)*)) ])))))
6365

@@ -71,15 +73,6 @@ query! {
7173
(#eq? @_api "api")
7274
(#match? @DEPENDS "^(depends|constrains|onchange)$"))
7375

74-
(call [
75-
(identifier) @_Field
76-
(attribute (identifier) @_fields (identifier) @_Field) ]
77-
(argument_list
78-
. ((comment)* . (string) @MODEL)?
79-
((keyword_argument (identifier) @FIELD_DESCRIPTOR (_)) ","?)*)
80-
(#eq? @_fields "fields")
81-
(#match? @_Field "^(Many2one|One2many|Many2many)$"))
82-
8376
((call
8477
(attribute
8578
(_) @MAPPED_TARGET (identifier) @_search)
@@ -305,9 +298,6 @@ impl Backend {
305298
if let Some(local_model) = match_.nodes_for_capture_index(PyCompletions::MappedTarget as _).next() {
306299
let model_ = (self.index).model_of_range(root, local_model.byte_range().map_unit(ByteOffset), contents)?;
307300
model = _R(model_).to_string();
308-
} else if let Some(field_model) = match_.nodes_for_capture_index(PyCompletions::Model as _).next() {
309-
// A sibling @MODEL node; this is defined on the `fields.*(comodel_name='@MODEL', domain=[..])` pattern
310-
model = String::from_utf8_lossy(&contents[field_model.byte_range().shrink(1)]).into_owned();
311301
} else if let Some(this_model) = &this_model {
312302
model = String::from_utf8_lossy(this_model).to_string();
313303
} else {
@@ -359,6 +349,7 @@ impl Backend {
359349
let query = PyCompletions::query();
360350
let mut cursor = tree_sitter::QueryCursor::new();
361351
let mut this_model = ThisModel::default();
352+
362353
for match_ in cursor.matches(query, root, contents) {
363354
for capture in match_.captures {
364355
let range = capture.node.byte_range();
@@ -376,12 +367,17 @@ impl Backend {
376367
.next()
377368
.map(|prop| matches!(&contents[prop.byte_range()], b"_name" | b"_inherit"))
378369
.unwrap_or(true);
379-
if is_meta && range.contains(&offset) {
370+
if range.contains(&offset) {
380371
let range = range.shrink(1);
381372
let slice = some!(rope.get_byte_slice(range.clone()));
382373
let slice = Cow::from(slice);
383374
return self.jump_def_model(&slice);
384-
} else if range.end < offset {
375+
} else if range.end < offset && is_meta
376+
// match_
377+
// .nodes_for_capture_index(PyCompletions::FieldType as _)
378+
// .next()
379+
// .is_none()
380+
{
385381
this_model.tag_model(capture.node, &match_, root.byte_range(), contents);
386382
}
387383
}
@@ -454,6 +450,7 @@ impl Backend {
454450
| Some(PyCompletions::Prop)
455451
| Some(PyCompletions::ReadFn)
456452
| Some(PyCompletions::Scope)
453+
| Some(PyCompletions::FieldType)
457454
| None => {}
458455
}
459456
}
@@ -601,7 +598,12 @@ impl Backend {
601598
let slice = Cow::from(slice);
602599
let slice = some!(_G(slice));
603600
return self.model_references(&path, &slice.into());
604-
} else if range.end < offset {
601+
} else if range.end < offset
602+
&& match_
603+
.nodes_for_capture_index(PyCompletions::FieldType as _)
604+
.next()
605+
.is_none()
606+
{
605607
this_model.tag_model(capture.node, &match_, root.byte_range(), contents);
606608
}
607609
}
@@ -639,6 +641,7 @@ impl Backend {
639641
| Some(PyCompletions::Prop)
640642
| Some(PyCompletions::ReadFn)
641643
| Some(PyCompletions::Scope)
644+
| Some(PyCompletions::FieldType)
642645
| None => {}
643646
}
644647
}
@@ -770,6 +773,7 @@ impl Backend {
770773
| Some(PyCompletions::ReadFn)
771774
| Some(PyCompletions::Scope)
772775
| Some(PyCompletions::Prop)
776+
| Some(PyCompletions::FieldType)
773777
| None => {}
774778
}
775779
}
@@ -799,10 +803,7 @@ impl Backend {
799803
}
800804
}
801805

802-
pub(crate) async fn python_signature_help(
803-
&self,
804-
params: SignatureHelpParams,
805-
) -> anyhow::Result<Option<SignatureHelp>> {
806+
pub(crate) fn python_signature_help(&self, params: SignatureHelpParams) -> anyhow::Result<Option<SignatureHelp>> {
806807
use std::fmt::Write;
807808

808809
let document =
@@ -856,7 +857,7 @@ impl Backend {
856857
};
857858
let method_key = some!(_G(&method));
858859
let rtype = (self.index).resolve_method_returntype(method_key.into(), model_key.into());
859-
let model = some!((self.index).models.get(&model_key.into()));
860+
let model = some!((self.index).models.get(&model_key));
860861
let method_obj = some!(some!(model.methods.as_ref()).get(&method_key.into()));
861862

862863
let mut label = format!("{}(", method);
@@ -944,21 +945,13 @@ impl<'this> ThisModel<'this> {
944945
top_level_range: core::ops::Range<usize>,
945946
contents: &'this [u8],
946947
) {
947-
// sanity check: if tagged as part of a call expression, it must be a relational field declaration
948-
match model.parent() {
949-
Some(parent) if parent.kind() == "argument_list" => {
950-
// (call (argument_list ..))
951-
let call = parent.parent().unwrap();
952-
// either fields.Many2one OR Many2one
953-
let mut attr = call.named_child(0).unwrap();
954-
if attr.kind() == "attribute" {
955-
attr = attr.named_child(1).unwrap();
956-
}
957-
if !matches!(&contents[attr.byte_range()], b"Many2one" | b"One2many" | b"Many2many") {
958-
return;
959-
}
960-
}
961-
_ => {}
948+
if match_
949+
.nodes_for_capture_index(PyCompletions::FieldType as _)
950+
.next()
951+
.is_some()
952+
{
953+
// debug_assert!(false, "tag_model called on a class model; handle this manually");
954+
return;
962955
}
963956

964957
debug_assert_eq!(model.kind(), "string");

0 commit comments

Comments
 (0)