Skip to content

Commit

Permalink
feat: xml-id references
Browse files Browse the repository at this point in the history
  • Loading branch information
Desdaemon committed Aug 26, 2023
1 parent 137aa4b commit 2722a4d
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 43 deletions.
3 changes: 3 additions & 0 deletions examples/one/views/records.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@
<record id="two" model="ir.ui.view">
<field name="inherit_id" ref="one"/>
</record>
<record id="three" model="ir.ui.view">
<field name="inherit_id" ref="one"/>
</record>
</odoo>
2 changes: 1 addition & 1 deletion examples/two/test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
self.env.ref('generic_tax_report')
self.env.ref('one.one')

class Foo(Model):
pass
Expand Down
3 changes: 2 additions & 1 deletion examples/two/views/templates.xml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<odoo>
<template id="asd" inherit_id='one.one'
<template id="asd" inherit_id='one.one'>
</template>
</odoo>
39 changes: 35 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,6 @@ impl LanguageServer for Backend {
if ext == "xml" {
location = self
.xml_jump_def(params, document.value().clone())
.await
.map_err(|err| error!("Error retrieving references:\n{err}"))
.ok()
.flatten();
Expand Down Expand Up @@ -380,6 +379,19 @@ impl LanguageServer for Backend {
Ok(None)
}
}
} else if ext == "xml" {
match self.xml_references(params, document.value().clone()) {
Ok(ret) => Ok(ret),
Err(report) => {
self.client
.show_message(
MessageType::ERROR,
format!("error during gathering python references:\n{report}"),
)
.await;
Ok(None)
}
}
} else {
Ok(None)
}
Expand All @@ -396,7 +408,7 @@ impl LanguageServer for Backend {
return Ok(None);
};
if ext == "xml" {
match self.xml_completions(params, document.value().clone()).await {
match self.xml_completions(params, document.value().clone()) {
Ok(ret) => Ok(ret),
Err(report) => {
self.client
Expand Down Expand Up @@ -521,8 +533,7 @@ impl Backend {
(None, Text::Delta(_)) => Err(diagnostic!("No rope and got delta"))?,
};
if matches!(split_uri, Some((_, "xml"))) || matches!(params.language, Some(Language::Xml)) {
self.on_change_xml(&params.text, &params.uri, rope, &mut diagnostics)
.await;
self.on_change_xml(&params.text, &params.uri, rope, &mut diagnostics);
} else if matches!(split_uri, Some((_, "py"))) || matches!(params.language, Some(Language::Python)) {
self.on_change_python(&params.text, &params.uri, rope, params.old_rope)
.await?;
Expand Down Expand Up @@ -713,6 +724,26 @@ impl Backend {
locations.extend(record_locations);
Ok(Some(locations))
}
fn record_references(
&self,
cursor_value: &str,
current_module: Option<&str>,
) -> miette::Result<Option<Vec<Location>>> {
let cursor_match = cursor_value.split_once('.');
let locations = self.module_index.records.iter().filter_map(|entry| {
match (&entry.module, &entry.inherit_id, cursor_match, current_module) {
(_, Some((Some(lhs_mod), lhs_id)), Some((rhs_mod, rhs_id)), _) => {
lhs_mod == rhs_mod && lhs_id == rhs_id
}
(lhs_mod, Some((None, lhs_id)), Some((rhs_mod, rhs_id)), _) => lhs_mod == rhs_mod && lhs_id == rhs_id,
(_, Some((Some(lhs_mod), xml_id)), None, Some(rhs_mod)) => lhs_mod == rhs_mod && xml_id == cursor_value,
(lhs_mod, Some((None, xml_id)), None, Some(rhs_mod)) => lhs_mod == rhs_mod && xml_id == cursor_value,
_ => false,
}
.then(|| entry.location.clone())
});
Ok(Some(locations.collect()))
}
async fn on_change_config(&self, config: Config) {
if let Some(SymbolsConfig { limit: Some(limit) }) = config.symbols {
self.symbols_limit.store(limit as usize, Relaxed);
Expand Down
39 changes: 29 additions & 10 deletions src/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,17 +181,36 @@ impl Backend {
let mut cursor = tree_sitter::QueryCursor::new();
cursor.set_match_limit(256);
cursor.set_byte_range(range);
let current_module = self
.module_index
.module_of_path(Path::new(params.text_document_position.text_document.uri.path()))
.map(|ref_| ref_.to_string());
'match_: for match_ in cursor.matches(query, ast.root_node(), &bytes[..]) {
for model in match_.nodes_for_capture_index(1) {
let range = model.byte_range();
if range.contains(&offset) {
let range = range.contract(1);
let Some(slice) = rope.get_byte_slice(range.clone()) else {
dbg!(&range);
break 'match_;
};
let slice = Cow::from(slice);
return self.model_references(&slice);
for capture in match_.captures {
if capture.index == 2 {
// @xml_id
let range = capture.node.byte_range();
if range.contains(&offset) {
let range = range.contract(1);
let Some(slice) = rope.get_byte_slice(range.clone()) else {
dbg!(&range);
break 'match_;
};
let slice = Cow::from(slice);
return self.record_references(&slice, current_module.as_deref());
}
} else if capture.index == 3 {
// @model
let range = capture.node.byte_range();
if range.contains(&offset) {
let range = range.contract(1);
let Some(slice) = rope.get_byte_slice(range.clone()) else {
dbg!(&range);
break 'match_;
};
let slice = Cow::from(slice);
return self.model_references(&slice);
}
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/queries/py_references.scm
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
((call
[(attribute (attribute (_) (identifier) @_env) (identifier) @_ref)
(attribute (identifier) @_env (identifier) @_ref)]
(argument_list . (string) @xml_id))
(#eq? @_env "env")
(#eq? @_ref "ref"))

((subscript
[(identifier) @_env
(attribute (_) (identifier) @_env)]
Expand Down
115 changes: 88 additions & 27 deletions src/xml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,20 @@ use xmlparser::{StrSpan, Token, Tokenizer};
use odoo_lsp::record::Record;
use odoo_lsp::utils::*;

enum RecordField {
InheritId,
Model,
Id,
}

enum Tag {
Field,
Template,
Record,
}

impl Backend {
pub async fn on_change_xml(&self, text: &Text, uri: &Url, rope: Rope, diagnostics: &mut Vec<Diagnostic>) {
pub fn on_change_xml(&self, text: &Text, uri: &Url, rope: Rope, diagnostics: &mut Vec<Diagnostic>) {
let text = match text {
Text::Full(full) => Cow::Borrowed(full.as_str()),
// Assume rope is up to date
Expand Down Expand Up @@ -115,11 +127,7 @@ impl Backend {

Ok((slice, cursor_by_char, relative_offset))
}
pub async fn xml_completions(
&self,
params: CompletionParams,
rope: Rope,
) -> miette::Result<Option<CompletionResponse>> {
pub fn xml_completions(&self, params: CompletionParams, rope: Rope) -> miette::Result<Option<CompletionResponse>> {
let position = params.text_document_position.position;
let uri = &params.text_document_position.text_document.uri;
let (slice, cursor_by_char, relative_offset) = self.record_slice(&rope, uri, position)?;
Expand All @@ -130,17 +138,6 @@ impl Backend {
.module_of_path(Path::new(uri.path()))
.expect("must be in a module");

enum Tag {
Record,
Template,
Field,
}

enum RecordField {
InheritId,
Model,
}

let mut items = vec![];
let mut model_filter = None;
let mut tag = None::<Tag>;
Expand Down Expand Up @@ -215,29 +212,79 @@ impl Backend {
&mut items,
)?,
RecordField::Model => self.complete_model(needle, replace_range, rope.clone(), &mut items)?,
RecordField::Id => return Ok(None),
}

Ok(Some(CompletionResponse::List(CompletionList {
is_incomplete: items.len() >= Self::LIMIT,
items,
})))
}
pub async fn xml_jump_def(&self, params: GotoDefinitionParams, rope: Rope) -> miette::Result<Option<Location>> {
pub fn xml_jump_def(&self, params: GotoDefinitionParams, rope: Rope) -> miette::Result<Option<Location>> {
let position = params.text_document_position_params.position;
let uri = &params.text_document_position_params.text_document.uri;
let (slice, cursor_by_char, _) = self.record_slice(&rope, uri, position)?;
let reader = Tokenizer::from(&slice[..]);

enum RecordField {
InheritId,
Model,
let mut record_field = None::<RecordField>;
let mut cursor_value = None::<StrSpan>;
let mut tag = None::<Tag>;

for token in reader {
match token {
Ok(Token::ElementStart { local, .. }) => match local.as_str() {
"field" => tag = Some(Tag::Field),
"template" => tag = Some(Tag::Template),
"record" => tag = Some(Tag::Record),
_ => {}
},
Ok(Token::Attribute { local, value, .. }) if matches!(tag, Some(Tag::Field)) => {
if local.as_str() == "ref" && value.range().contains(&cursor_by_char) {
cursor_value = Some(value);
} else if local.as_str() == "name" && value.as_str() == "inherit_id" {
record_field = Some(RecordField::InheritId);
}
}
Ok(Token::Attribute { local, value, .. })
if matches!(tag, Some(Tag::Template))
&& local.as_str() == "inherit_id"
&& value.range().contains(&cursor_by_char) =>
{
cursor_value = Some(value);
record_field = Some(RecordField::InheritId);
}
Ok(Token::Attribute { local, value, .. })
if matches!(tag, Some(Tag::Record))
&& local.as_str() == "model"
&& value.range().contains(&cursor_by_char) =>
{
cursor_value = Some(value);
record_field = Some(RecordField::Model);
}
Ok(Token::ElementEnd { .. }) if cursor_value.is_some() => break,
Err(_) => break,
Ok(token) => {
if token_span(&token).start() > cursor_by_char {
break;
}
}
}
}

enum Tag {
Field,
Template,
Record,
let Some(cursor_value) = cursor_value else {
return Ok(None);
};
match record_field {
Some(RecordField::InheritId) => self.jump_def_inherit_id(&cursor_value, uri),
Some(RecordField::Model) => self.jump_def_model(&cursor_value),
Some(RecordField::Id) | None => Ok(None),
}
}
pub fn xml_references(&self, params: ReferenceParams, rope: Rope) -> miette::Result<Option<Vec<Location>>> {
let position = params.text_document_position.position;
let uri = &params.text_document_position.text_document.uri;
let (slice, cursor_by_char, _) = self.record_slice(&rope, uri, position)?;
let reader = Tokenizer::from(&slice[..]);

let mut record_field = None::<RecordField>;
let mut cursor_value = None::<StrSpan>;
Expand Down Expand Up @@ -274,6 +321,14 @@ impl Backend {
cursor_value = Some(value);
record_field = Some(RecordField::Model);
}
Ok(Token::Attribute { local, value, .. })
if matches!(tag, Some(Tag::Record | Tag::Template))
&& local.as_str() == "id"
&& value.range().contains(&cursor_by_char) =>
{
cursor_value = Some(value);
record_field = Some(RecordField::Id);
}
Ok(Token::ElementEnd { .. }) if cursor_value.is_some() => break,
Err(_) => break,
Ok(token) => {
Expand All @@ -287,9 +342,15 @@ impl Backend {
let Some(cursor_value) = cursor_value else {
return Ok(None);
};
let current_module = self
.module_index
.module_of_path(Path::new(params.text_document_position.text_document.uri.path()))
.map(|ref_| ref_.to_string());
match record_field {
Some(RecordField::InheritId) => self.jump_def_inherit_id(&cursor_value, uri),
Some(RecordField::Model) => self.jump_def_model(&cursor_value),
Some(RecordField::Model) => self.model_references(&cursor_value),
Some(RecordField::InheritId) | Some(RecordField::Id) => {
self.record_references(&cursor_value, current_module.as_deref())
}
None => Ok(None),
}
}
Expand Down

0 comments on commit 2722a4d

Please sign in to comment.