Skip to content

Commit 458f570

Browse files
committed
feat: relational fields
1 parent 642b104 commit 458f570

File tree

7 files changed

+76
-50
lines changed

7 files changed

+76
-50
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ language-servers = ["odoo-lsp"]
5656

5757
[[language]]
5858
name = "python"
59+
# Order is important here
5960
language-servers = [
6061
"odoo-lsp",
6162
# add the default language servers here

examples/.helix/languages.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[language-server]
2-
odoo-lsp.command = "../target/debug/odoo-lsp"
2+
odoo-lsp.command = "cargo"
3+
odoo-lsp.args = ["run"]
34

45
[[language]]
56
name = "xml"

examples/two/test.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
self.env.ref('one.view_one')
1+
self.env.ref('generic_tax_report')
22

33
class Foo(Model):
44
pass
@@ -11,4 +11,6 @@ class Baz(models.Model):
1111

1212
class Quux(models.Model):
1313
_name = 'quux'
14-
_inherit = 'bar'
14+
_inherit = 'bar'
15+
16+
what = fields.Many2one(comodel_name='bar')

src/model.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,13 @@ impl ModelIndex {
7070
uri: uri.clone(),
7171
range: item.range,
7272
}));
73+
} else if let Some(base) = &entry.0 {
74+
eprintln!("{} already defined at {base}", entry.key());
7375
} else {
74-
if let Some(base) = &entry.0 {
75-
eprintln!("{} already defined at {base}", entry.key());
76-
} else {
77-
entry.0 = Some(ModelLocation(Location {
78-
uri: uri.clone(),
79-
range: item.range,
80-
}))
81-
}
76+
entry.0 = Some(ModelLocation(Location {
77+
uri: uri.clone(),
78+
range: item.range,
79+
}))
8280
}
8381
}
8482
}

src/python.rs

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ impl Backend {
4343
self.update_ast(text, uri, rope, parser)?;
4444
Ok(())
4545
}
46+
const BYTE_WINDOW: usize = 200;
4647
pub fn python_completions(
4748
&self,
4849
params: CompletionParams,
@@ -64,14 +65,15 @@ impl Backend {
6465
cursor.set_match_limit(256);
6566
let bytes = rope.bytes().collect::<Vec<_>>();
6667
// TODO: Very inexact, is there a better way?
67-
let range = offset.saturating_sub(50)..bytes.len().min(offset + 200);
68+
let range = offset.saturating_sub(Self::BYTE_WINDOW)..bytes.len().min(offset + Self::BYTE_WINDOW);
6869
let query = py_completions();
6970
cursor.set_byte_range(range.clone());
7071
let mut items = vec![];
7172
'match_: for match_ in cursor.matches(query, ast.root_node(), &bytes[..]) {
72-
match match_.captures {
73-
[_, _, xml_id] => {
74-
let range = xml_id.node.byte_range();
73+
for capture in match_.captures {
74+
if capture.index == 2 {
75+
// @xml_id
76+
let range = capture.node.byte_range();
7577
if range.contains(&offset) {
7678
let Some(slice) = rope.get_byte_slice(range.clone()) else {
7779
dbg!(&range);
@@ -87,9 +89,9 @@ impl Backend {
8789
items,
8890
})));
8991
}
90-
}
91-
[_, model] => {
92-
let range = model.node.byte_range();
92+
} else if capture.index == 3 {
93+
// @model
94+
let range = capture.node.byte_range();
9395
if range.contains(&offset) {
9496
let Some(slice) = rope.get_byte_slice(range.clone()) else {
9597
dbg!(&range);
@@ -105,32 +107,31 @@ impl Backend {
105107
})));
106108
}
107109
}
108-
unk => Err(diagnostic!("Unknown pattern {unk:?}"))?,
109110
}
110111
}
111112
Ok(None)
112113
}
113114
pub fn python_jump_def(&self, params: GotoDefinitionParams, rope: Rope) -> miette::Result<Option<Location>> {
114-
let Some(ast) = self
115+
let uri = &params.text_document_position_params.text_document.uri;
116+
let ast = self
115117
.ast_map
116-
.get(params.text_document_position_params.text_document.uri.path())
117-
else {
118-
return Ok(None);
119-
};
118+
.get(uri.path())
119+
.ok_or_else(|| diagnostic!("Did not build AST for {}", uri.path()))?;
120120
let Some(ByteOffset(offset)) = position_to_offset(params.text_document_position_params.position, rope.clone())
121121
else {
122-
return Ok(None);
122+
Err(diagnostic!("could not find offset for {}", uri.path()))?
123123
};
124124
let query = py_completions();
125125
let bytes = rope.bytes().collect::<Vec<_>>();
126-
let range = offset.saturating_sub(50)..bytes.len().min(offset + 200);
126+
let range = offset.saturating_sub(Self::BYTE_WINDOW)..bytes.len().min(offset + Self::BYTE_WINDOW);
127127
let mut cursor = tree_sitter::QueryCursor::new();
128128
cursor.set_match_limit(256);
129-
cursor.set_byte_range(range.clone());
129+
cursor.set_byte_range(range);
130130
'match_: for match_ in cursor.matches(query, ast.root_node(), &bytes[..]) {
131-
match match_.captures {
132-
[_, _, xml_id] => {
133-
let range = xml_id.node.byte_range();
131+
for capture in match_.captures {
132+
if capture.index == 2 {
133+
// @xml_id
134+
let range = capture.node.byte_range();
134135
if range.contains(&offset) {
135136
let range = range.contract(1);
136137
let Some(slice) = rope.get_byte_slice(range.clone()) else {
@@ -141,9 +142,9 @@ impl Backend {
141142
return self
142143
.jump_def_inherit_id(&slice, &params.text_document_position_params.text_document.uri);
143144
}
144-
}
145-
[_, model] => {
146-
let range = model.node.byte_range();
145+
} else if capture.index == 3 {
146+
// @model
147+
let range = capture.node.byte_range();
147148
if range.contains(&offset) {
148149
let range = range.contract(1);
149150
let Some(slice) = rope.get_byte_slice(range.clone()) else {
@@ -154,7 +155,6 @@ impl Backend {
154155
return self.jump_def_model(&slice);
155156
}
156157
}
157-
unk => Err(diagnostic!("Unknown pattern {unk:?}"))?,
158158
}
159159
}
160160
Ok(None)
@@ -170,25 +170,22 @@ impl Backend {
170170
};
171171
let query = py_references();
172172
let bytes = rope.bytes().collect::<Vec<_>>();
173-
let range = offset.saturating_sub(50)..bytes.len().min(offset + 200);
173+
let range = offset.saturating_sub(Self::BYTE_WINDOW)..bytes.len().min(offset + Self::BYTE_WINDOW);
174174
let mut cursor = tree_sitter::QueryCursor::new();
175175
cursor.set_match_limit(256);
176-
cursor.set_byte_range(range.clone());
176+
cursor.set_byte_range(range);
177177
'match_: for match_ in cursor.matches(query, ast.root_node(), &bytes[..]) {
178-
match match_.captures {
179-
[_, model] => {
180-
let range = model.node.byte_range();
181-
if range.contains(&offset) {
182-
let range = range.contract(1);
183-
let Some(slice) = rope.get_byte_slice(range.clone()) else {
184-
dbg!(&range);
185-
break 'match_;
186-
};
187-
let slice = Cow::from(slice);
188-
return self.model_references(&slice);
189-
}
178+
for model in match_.nodes_for_capture_index(1) {
179+
let range = dbg!(&model).byte_range();
180+
if range.contains(&offset) {
181+
let range = range.contract(1);
182+
let Some(slice) = rope.get_byte_slice(range.clone()) else {
183+
dbg!(&range);
184+
break 'match_;
185+
};
186+
let slice = Cow::from(slice);
187+
return self.model_references(&slice);
190188
}
191-
unk => Err(diagnostic!("Unknown pattern {unk:?}"))?,
192189
}
193190
}
194191
Ok(None)

src/queries/py_completions.scm

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,14 @@
1717
(assignment
1818
(identifier) @_inherit
1919
(string) @model))))
20-
(#eq? @_inherit "_inherit"))
20+
(#eq? @_inherit "_inherit"))
21+
22+
((call
23+
[(identifier) @_Field
24+
(attribute (identifier) @_fields (identifier) @_Field)]
25+
[(argument_list . (string) @model)
26+
(argument_list
27+
(keyword_argument (identifier) @_comodel_name (string) @model))])
28+
(#eq? @_fields "fields")
29+
(#eq? @_comodel_name "comodel_name")
30+
(#match? @_Field "^(Many2one|One2many|Many2many)$"))

src/queries/py_references.scm

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,24 @@
1+
((subscript
2+
[(identifier) @_env
3+
(attribute (_) (identifier) @_env)]
4+
(string) @model)
5+
(#eq? @_env "env"))
6+
17
((class_definition
28
(block
39
(expression_statement
410
(assignment
511
(identifier) @_field
612
(string) @model))))
713
(#match? @_field "^_(name|inherit)$"))
14+
15+
((call
16+
[(identifier) @_Field
17+
(attribute (identifier) @_fields (identifier) @_Field)]
18+
[(argument_list . (string) @model)
19+
(argument_list
20+
(keyword_argument (identifier) @_comodel_name (string) @model))])
21+
(#eq? @_fields "fields")
22+
(#eq? @_comodel_name "comodel_name")
23+
(#match? @_Field "^(Many2one|One2many|Many2many)$"))
24+

0 commit comments

Comments
 (0)