use std::{
str::FromStr,
sync::{Arc, RwLock},
};
use tower_lsp_server::ls_types::{self, GotoDefinitionResponse, Location, Position, Range};
use url::Url;
pub fn response(hir: Arc<RwLock<mq_hir::Hir>>, url: Url, position: Position) -> Option<GotoDefinitionResponse> {
let hir_guard = hir.read().unwrap();
let source = hir_guard.source_by_url(&url);
if let Some(source) = source {
if let Some((_, symbol)) = hir_guard.find_symbol_in_position(
source,
mq_lang::Position::new(position.line + 1, (position.character + 1) as usize),
) {
symbol.source.text_range.and_then(|text_range| {
let target_url = symbol
.source
.source_id
.and_then(|sid| hir_guard.url_by_source(&sid))
.cloned()?;
let uri = match ls_types::Uri::from_str(target_url.as_ref()) {
Ok(uri) => uri,
Err(_) => return None,
};
Some(GotoDefinitionResponse::Scalar(Location {
uri,
range: Range {
start: Position {
line: text_range.start.line - 1,
character: (text_range.start.column - 1) as u32,
},
end: Position {
line: text_range.end.line - 1,
character: (text_range.end.column - 1) as u32,
},
},
}))
})
} else {
None
}
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use mq_hir::Hir;
#[test]
fn test_goto_definition_found() {
let mut hir = Hir::default();
let url = Url::parse("file:///test.mq").unwrap();
hir.add_code(Some(url.clone()), "def func1(): 1; func1()");
let result = response(Arc::new(RwLock::new(hir)), url.clone(), Position::new(0, 4));
assert!(result.is_some());
if let Some(GotoDefinitionResponse::Scalar(location)) = result {
assert_eq!(location.uri.to_string(), url.to_string());
assert_eq!(location.range.start, Position::new(0, 4));
assert_eq!(location.range.end, Position::new(0, 9));
} else {
panic!("Expected Scalar response");
}
}
#[test]
fn test_goto_definition_not_found() {
let hir = Arc::new(RwLock::new(mq_hir::Hir::default()));
let url = Url::parse("file:///test.mq").unwrap();
let result = response(hir, url, Position::new(0, 0));
assert!(result.is_none());
}
#[test]
fn test_goto_definition_no_text_range() {
let mut hir = mq_hir::Hir::default();
let url = Url::parse("file:///test.mq").unwrap();
hir.add_code(Some(url.clone()), "let x = 42;");
let result = response(Arc::new(RwLock::new(hir)), url, Position::new(0, 11));
assert!(result.is_none());
}
#[test]
fn test_goto_definition_call_to_definition() {
let mut hir = Hir::default();
let url = Url::parse("file:///test.mq").unwrap();
hir.add_code(Some(url.clone()), "def helper(): 42 end | helper()");
let result = response(Arc::new(RwLock::new(hir)), url.clone(), Position::new(0, 24));
assert!(result.is_some());
if let Some(GotoDefinitionResponse::Scalar(location)) = result {
assert_eq!(location.uri.to_string(), url.to_string());
assert_eq!(location.range.start, Position::new(0, 4));
} else {
panic!("Expected Scalar response");
}
}
#[test]
fn test_goto_definition_builtin_returns_none() {
let mut hir = Hir::default();
let url = Url::parse("file:///test.mq").unwrap();
hir.add_code(Some(url.clone()), "\"test\" | len");
let result = response(Arc::new(RwLock::new(hir)), url.clone(), Position::new(0, 10));
assert!(result.is_none());
}
}