From fff627d2e76fed9dc91ace078e71811135fa9431 Mon Sep 17 00:00:00 2001 From: Viet Dinh <54ckb0y789@gmail.com> Date: Wed, 23 Aug 2023 23:44:12 -0400 Subject: [PATCH] feat: model name references --- src/main.rs | 64 +++++++++++++++++++++++--- src/python.rs | 87 ++++++++++++++++++++++++++++++----- src/queries/py_references.scm | 7 +++ 3 files changed, 139 insertions(+), 19 deletions(-) create mode 100644 src/queries/py_references.scm diff --git a/src/main.rs b/src/main.rs index 2325c29..46724c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ use tree_sitter::{Parser, Tree}; use odoo_lsp::config::{Config, ModuleConfig}; use odoo_lsp::index::ModuleIndex; +use odoo_lsp::model::ModelLocation; use odoo_lsp::{format_loc, utils::*}; mod catch_panic; @@ -335,7 +336,6 @@ impl LanguageServer for Backend { } else if ext == "py" { location = self .python_jump_def(params, document.value().clone()) - .await .map_err(|err| eprintln!("Error retrieving references:\n{err}")) .ok() .flatten(); @@ -346,8 +346,37 @@ impl LanguageServer for Backend { Ok(location.map(GotoDefinitionResponse::Scalar)) } - async fn references(&self, _: ReferenceParams) -> Result>> { - Ok(None) + async fn references(&self, params: ReferenceParams) -> Result>> { + let uri = ¶ms.text_document_position.text_document.uri; + eprintln!("references {}", uri.path()); + + let Some((_, ext)) = uri.path().rsplit_once('.') else { + return Ok(None); // hit a directory, super unlikely + }; + let Some(document) = self.document_map.get(uri.path()) else { + eprintln!("Bug: did not build a rope for {}", uri.path()); + return Ok(None); + }; + if ext == "py" { + let Some(ast) = self.ast_map.get(uri.path()) else { + eprintln!("Bug: did not build AST for {}", uri.path()); + return Ok(None); + }; + match self.python_references(params, document.value().clone(), ast.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) + } } async fn completion(&self, params: CompletionParams) -> Result> { let uri = ¶ms.text_document_position.text_document.uri; @@ -375,10 +404,7 @@ impl LanguageServer for Backend { eprintln!("Bug: did not build AST for {}", uri.path()); return Ok(None); }; - match self - .python_completions(params, ast.value().clone(), document.value().clone()) - .await - { + match self.python_completions(params, ast.value().clone(), document.value().clone()) { Ok(ret) => Ok(ret), Err(err) => { self.client @@ -616,6 +642,30 @@ impl Backend { } return Ok((self.module_index.records.get(value.as_ref())).map(|entry| entry.location.clone())); } + fn jump_def_model(&self, cursor_value: &str) -> miette::Result> { + match self + .module_index + .models + .get(cursor_value) + .and_then(|entry| entry.0.as_ref().cloned()) + { + Some(ModelLocation(base)) => Ok(Some(base)), + None => Ok(None), + } + } + fn model_references(&self, cursor_value: &str) -> miette::Result>> { + let mut locations = match self.module_index.models.get(cursor_value) { + Some(entry) => entry.1.iter().map(|loc| loc.0.clone()).collect::>(), + None => vec![], + }; + let record_locations = self + .module_index + .records + .iter() + .filter_map(|record| (record.model.as_deref() == Some(cursor_value)).then(|| record.location.clone())); + locations.extend(record_locations); + Ok(Some(locations)) + } async fn on_change_config(&self, config: Config) { let Some(ModuleConfig { roots: Some(roots), .. }) = config.module else { return; diff --git a/src/python.rs b/src/python.rs index 0b07946..c4c3338 100644 --- a/src/python.rs +++ b/src/python.rs @@ -22,6 +22,17 @@ fn py_completions() -> &'static Query { }) } +fn py_references() -> &'static Query { + static QUERY: OnceLock = OnceLock::new(); + QUERY.get_or_init(|| { + tree_sitter::Query::new( + tree_sitter_python::language(), + include_str!("queries/py_references.scm"), + ) + .unwrap() + }) +} + impl Backend { pub async fn on_change_python(&self, text: &Text, uri: &Url, rope: Rope) -> miette::Result<()> { let mut parser = Parser::new(); @@ -32,7 +43,7 @@ impl Backend { self.update_ast(text, uri, rope, parser)?; Ok(()) } - pub async fn python_completions( + pub fn python_completions( &self, params: CompletionParams, ast: Tree, @@ -99,7 +110,7 @@ impl Backend { } Ok(None) } - pub async fn python_jump_def(&self, params: GotoDefinitionParams, rope: Rope) -> miette::Result> { + pub fn python_jump_def(&self, params: GotoDefinitionParams, rope: Rope) -> miette::Result> { let Some(ast) = self .ast_map .get(params.text_document_position_params.text_document.uri.path()) @@ -116,17 +127,69 @@ impl Backend { let mut cursor = tree_sitter::QueryCursor::new(); cursor.set_match_limit(256); cursor.set_byte_range(range.clone()); - 'match_: for match_ in cursor.matches(query, ast.root_node(), bytes.as_slice()) { - for xml_id in match_.nodes_for_capture_index(2) { - if xml_id.byte_range().contains(&offset) { - let range = xml_id.range().start_byte + 1..xml_id.range().end_byte - 1; - let Some(slice) = rope.get_byte_slice(range.clone()) else { - dbg!((xml_id.byte_range(), &range)); - break 'match_; - }; - let slice = Cow::from(slice); - return self.jump_def_inherit_id(&slice, ¶ms.text_document_position_params.text_document.uri); + 'match_: for match_ in cursor.matches(query, ast.root_node(), &bytes[..]) { + match match_.captures { + [_, _, xml_id] => { + let range = xml_id.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 + .jump_def_inherit_id(&slice, ¶ms.text_document_position_params.text_document.uri); + } + } + [_, model] => { + let range = model.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.jump_def_model(&slice); + } + } + unk => Err(diagnostic!("Unknown pattern {unk:?}"))?, + } + } + Ok(None) + } + pub fn python_references( + &self, + params: ReferenceParams, + rope: Rope, + ast: Tree, + ) -> miette::Result>> { + let Some(ByteOffset(offset)) = position_to_offset(params.text_document_position.position, rope.clone()) + else { + return Ok(None); + }; + let query = py_references(); + let bytes = rope.bytes().collect::>(); + let range = offset.saturating_sub(50)..bytes.len().min(offset + 200); + let mut cursor = tree_sitter::QueryCursor::new(); + cursor.set_match_limit(256); + cursor.set_byte_range(range.clone()); + 'match_: for match_ in cursor.matches(query, ast.root_node(), &bytes[..]) { + match match_.captures { + [_, model] => { + let range = model.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); + } } + unk => Err(diagnostic!("Unknown pattern {unk:?}"))?, } } Ok(None) diff --git a/src/queries/py_references.scm b/src/queries/py_references.scm new file mode 100644 index 0000000..f1154ab --- /dev/null +++ b/src/queries/py_references.scm @@ -0,0 +1,7 @@ +((class_definition + (block + (expression_statement + (assignment + (identifier) @_field + (string) @model)))) + (#match? @_field "^_(name|inherit)$"))