use std::collections::HashMap;
use lsp_types::{GotoDefinitionParams, GotoDefinitionResponse, Location, Position, Range, Uri};
use crate::ir::ast::{ClassDefinition, Name, StoredDefinition, Token};
use crate::ir::transform::scope_resolver::{ImportResolver, ScopeResolver};
use crate::lsp::utils::{
get_qualified_name_at_position, get_word_at_position, parse_document, token_to_range,
};
use crate::lsp::workspace::WorkspaceState;
pub fn handle_goto_definition(
documents: &HashMap<Uri, String>,
params: GotoDefinitionParams,
) -> Option<GotoDefinitionResponse> {
let uri = ¶ms.text_document_position_params.text_document.uri;
let position = params.text_document_position_params.position;
let text = documents.get(uri)?;
let path = uri.path().as_str();
let word = get_word_at_position(text, position)?;
if let Ok(result) = crate::Compiler::new().compile_str(text, path)
&& let Some(token) = find_definition_in_ast(&result.def, &word)
{
return Some(GotoDefinitionResponse::Scalar(Location {
uri: uri.clone(),
range: token_to_range(token),
}));
}
None
}
pub fn handle_goto_definition_workspace(
workspace: &mut WorkspaceState,
params: GotoDefinitionParams,
) -> Option<GotoDefinitionResponse> {
let uri = ¶ms.text_document_position_params.text_document.uri;
let position = params.text_document_position_params.position;
let text = workspace.get_document(uri)?;
let path = uri.path().as_str();
let word = get_word_at_position(text, position)?;
let qualified_name = get_qualified_name_at_position(text, position);
if let Some(ast) = parse_document(text, path) {
if let Some(ref qn) = qualified_name
&& let Some(response) = try_resolve_qualified_name(&ast, qn, workspace)
{
return Some(response);
}
if let Some(import_path) = find_import_alias_in_ast(&ast, &word) {
workspace.ensure_package_indexed(&import_path);
if let Some(sym) = workspace.lookup_symbol(&import_path) {
return Some(GotoDefinitionResponse::Scalar(Location {
uri: sym.uri.clone(),
range: Range {
start: Position {
line: sym.line,
character: sym.column,
},
end: Position {
line: sym.line,
character: sym.column
+ import_path.rsplit('.').next().unwrap_or(&import_path).len()
as u32,
},
},
}));
}
}
if let Some(within_name) = &ast.within
&& let Some(within_path) = get_within_path_for_word(within_name, &word)
&& let Some(sym) = workspace.lookup_symbol(&within_path)
{
return Some(GotoDefinitionResponse::Scalar(Location {
uri: sym.uri.clone(),
range: Range {
start: Position {
line: sym.line,
character: sym.column,
},
end: Position {
line: sym.line,
character: sym.column + word.len() as u32,
},
},
}));
}
if let Some(response) = try_resolve_type_with_imports(&ast, &word, workspace, uri) {
return Some(response);
}
if let Some(token) = find_definition_in_ast(&ast, &word) {
return Some(GotoDefinitionResponse::Scalar(Location {
uri: uri.clone(),
range: token_to_range(token),
}));
}
let resolver = ScopeResolver::new(&ast);
if let Some(containing_class) =
resolver.class_at_0indexed(position.line, position.character)
{
let class_name = get_qualified_class_name(&ast, &containing_class.name.text);
if let Some(sym) = workspace.resolve_type(&word, uri, Some(&class_name)) {
return Some(GotoDefinitionResponse::Scalar(Location {
uri: sym.uri.clone(),
range: Range {
start: Position {
line: sym.line,
character: sym.column,
},
end: Position {
line: sym.line,
character: sym.column + word.len() as u32,
},
},
}));
}
}
}
if let Some(sym) = workspace.lookup_symbol(&word) {
return Some(GotoDefinitionResponse::Scalar(Location {
uri: sym.uri.clone(),
range: Range {
start: Position {
line: sym.line,
character: sym.column,
},
end: Position {
line: sym.line,
character: sym.column + word.len() as u32,
},
},
}));
}
let simple_name = word.rsplit('.').next().unwrap_or(&word);
let matches = workspace.lookup_by_simple_name(simple_name);
if matches.len() == 1 {
let sym = matches[0];
return Some(GotoDefinitionResponse::Scalar(Location {
uri: sym.uri.clone(),
range: Range {
start: Position {
line: sym.line,
character: sym.column,
},
end: Position {
line: sym.line,
character: sym.column + simple_name.len() as u32,
},
},
}));
} else if matches.len() > 1 {
let locations: Vec<Location> = matches
.iter()
.map(|sym| Location {
uri: sym.uri.clone(),
range: Range {
start: Position {
line: sym.line,
character: sym.column,
},
end: Position {
line: sym.line,
character: sym.column + simple_name.len() as u32,
},
},
})
.collect();
return Some(GotoDefinitionResponse::Array(locations));
}
None
}
fn get_qualified_class_name(ast: &StoredDefinition, class_name: &str) -> String {
if let Some(within) = &ast.within {
format!("{}.{}", within, class_name)
} else {
class_name.to_string()
}
}
fn try_resolve_qualified_name(
ast: &StoredDefinition,
qualified_name: &str,
workspace: &mut WorkspaceState,
) -> Option<GotoDefinitionResponse> {
let parts: Vec<&str> = qualified_name.split('.').collect();
if parts.is_empty() {
return None;
}
let first_part = parts[0];
let rest_parts = &parts[1..];
for class in ast.class_list.values() {
if let Some(response) =
try_resolve_import_in_class(class, first_part, rest_parts, workspace)
{
return Some(response);
}
}
if let Some(sym) = workspace.lookup_symbol(qualified_name) {
return Some(GotoDefinitionResponse::Scalar(Location {
uri: sym.uri.clone(),
range: Range {
start: Position {
line: sym.line,
character: sym.column,
},
end: Position {
line: sym.line,
character: sym.column
+ qualified_name
.rsplit('.')
.next()
.unwrap_or(qualified_name)
.len() as u32,
},
},
}));
}
None
}
fn try_resolve_import_in_class(
class: &ClassDefinition,
first_part: &str,
rest_parts: &[&str],
workspace: &mut WorkspaceState,
) -> Option<GotoDefinitionResponse> {
let import_resolver = ImportResolver::from_imports(&class.imports);
if let Some(resolved_path) = import_resolver.resolve(first_part) {
let full_qualified = if rest_parts.is_empty() {
resolved_path.to_string()
} else {
format!("{}.{}", resolved_path, rest_parts.join("."))
};
workspace.ensure_package_indexed(&full_qualified);
if let Some(sym) = workspace.lookup_symbol(&full_qualified) {
return Some(GotoDefinitionResponse::Scalar(Location {
uri: sym.uri.clone(),
range: Range {
start: Position {
line: sym.line,
character: sym.column,
},
end: Position {
line: sym.line,
character: sym.column
+ full_qualified
.rsplit('.')
.next()
.unwrap_or(&full_qualified)
.len() as u32,
},
},
}));
}
let simple = full_qualified.rsplit('.').next().unwrap_or(&full_qualified);
let matches = workspace.lookup_by_simple_name(simple);
if matches.len() == 1 {
let sym = matches[0];
return Some(GotoDefinitionResponse::Scalar(Location {
uri: sym.uri.clone(),
range: Range {
start: Position {
line: sym.line,
character: sym.column,
},
end: Position {
line: sym.line,
character: sym.column + simple.len() as u32,
},
},
}));
}
}
for nested in class.classes.values() {
if let Some(response) =
try_resolve_import_in_class(nested, first_part, rest_parts, workspace)
{
return Some(response);
}
}
None
}
fn try_resolve_type_with_imports(
ast: &StoredDefinition,
word: &str,
workspace: &mut WorkspaceState,
_uri: &Uri,
) -> Option<GotoDefinitionResponse> {
for class in ast.class_list.values() {
if let Some(response) = try_resolve_in_class_imports(class, word, workspace) {
return Some(response);
}
}
None
}
fn try_resolve_in_class_imports(
class: &ClassDefinition,
word: &str,
workspace: &mut WorkspaceState,
) -> Option<GotoDefinitionResponse> {
let import_resolver = ImportResolver::from_imports(&class.imports);
if let Some(resolved_path) = import_resolver.resolve(word) {
workspace.ensure_package_indexed(resolved_path);
if let Some(sym) = workspace.lookup_symbol(resolved_path) {
return Some(GotoDefinitionResponse::Scalar(Location {
uri: sym.uri.clone(),
range: Range {
start: Position {
line: sym.line,
character: sym.column,
},
end: Position {
line: sym.line,
character: sym.column + word.len() as u32,
},
},
}));
}
}
for import in &class.imports {
if let crate::ir::ast::Import::Unqualified { path, .. } = import {
let qualified = format!("{}.{}", path, word);
workspace.ensure_package_indexed(&qualified);
if let Some(sym) = workspace.lookup_symbol(&qualified) {
return Some(GotoDefinitionResponse::Scalar(Location {
uri: sym.uri.clone(),
range: Range {
start: Position {
line: sym.line,
character: sym.column,
},
end: Position {
line: sym.line,
character: sym.column + word.len() as u32,
},
},
}));
}
}
}
for nested in class.classes.values() {
if let Some(response) = try_resolve_in_class_imports(nested, word, workspace) {
return Some(response);
}
}
None
}
fn find_definition_in_ast<'a>(def: &'a StoredDefinition, name: &str) -> Option<&'a Token> {
for class in def.class_list.values() {
if let Some(token) = find_definition_in_class(class, name) {
return Some(token);
}
}
None
}
fn find_definition_in_class<'a>(class: &'a ClassDefinition, name: &str) -> Option<&'a Token> {
if class.name.text == name {
return Some(&class.name);
}
for (comp_name, comp) in &class.components {
if comp_name == name {
return Some(&comp.name_token);
}
}
for nested_class in class.classes.values() {
if let Some(token) = find_definition_in_class(nested_class, name) {
return Some(token);
}
}
None
}
fn find_import_alias_in_ast(def: &StoredDefinition, alias: &str) -> Option<String> {
for class in def.class_list.values() {
if let Some(path) = find_import_alias_in_class(class, alias) {
return Some(path);
}
}
None
}
fn find_import_alias_in_class(class: &ClassDefinition, alias: &str) -> Option<String> {
let import_resolver = ImportResolver::from_imports(&class.imports);
if let Some(path) = import_resolver.resolve(alias) {
return Some(path.to_string());
}
for nested_class in class.classes.values() {
if let Some(path) = find_import_alias_in_class(nested_class, alias) {
return Some(path);
}
}
None
}
fn get_within_path_for_word(within_name: &Name, word: &str) -> Option<String> {
let mut path_parts = Vec::new();
for token in &within_name.name {
path_parts.push(token.text.as_str());
if token.text == word {
return Some(path_parts.join("."));
}
}
None
}