Skip to content

Commit

Permalink
feat: model name references
Browse files Browse the repository at this point in the history
  • Loading branch information
Desdaemon committed Aug 24, 2023
1 parent 056d248 commit fff627d
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 19 deletions.
64 changes: 57 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -346,8 +346,37 @@ impl LanguageServer for Backend {

Ok(location.map(GotoDefinitionResponse::Scalar))
}
async fn references(&self, _: ReferenceParams) -> Result<Option<Vec<Location>>> {
Ok(None)
async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
let uri = &params.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<Option<CompletionResponse>> {
let uri = &params.text_document_position.text_document.uri;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<Option<Location>> {
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<Option<Vec<Location>>> {
let mut locations = match self.module_index.models.get(cursor_value) {
Some(entry) => entry.1.iter().map(|loc| loc.0.clone()).collect::<Vec<_>>(),
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;
Expand Down
87 changes: 75 additions & 12 deletions src/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ fn py_completions() -> &'static Query {
})
}

fn py_references() -> &'static Query {
static QUERY: OnceLock<Query> = 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();
Expand All @@ -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,
Expand Down Expand Up @@ -99,7 +110,7 @@ impl Backend {
}
Ok(None)
}
pub async fn python_jump_def(&self, params: GotoDefinitionParams, rope: Rope) -> miette::Result<Option<Location>> {
pub fn python_jump_def(&self, params: GotoDefinitionParams, rope: Rope) -> miette::Result<Option<Location>> {
let Some(ast) = self
.ast_map
.get(params.text_document_position_params.text_document.uri.path())
Expand All @@ -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, &params.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, &params.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<Option<Vec<Location>>> {
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::<Vec<_>>();
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)
Expand Down
7 changes: 7 additions & 0 deletions src/queries/py_references.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
((class_definition
(block
(expression_statement
(assignment
(identifier) @_field
(string) @model))))
(#match? @_field "^_(name|inherit)$"))

0 comments on commit fff627d

Please sign in to comment.