use crate::ir::ast::{Location, Token};
use lsp_types::{Position, Range};
pub use crate::compiler::{parse_file_cached, parse_source_simple as parse_document};
pub fn get_text_before_cursor(text: &str, position: Position) -> Option<String> {
let lines: Vec<&str> = text.lines().collect();
let line = lines.get(position.line as usize)?;
let col = (position.character as usize).min(line.len());
Some(line[..col].to_string())
}
pub fn get_word_at_position(text: &str, position: Position) -> Option<String> {
let lines: Vec<&str> = text.lines().collect();
let line = lines.get(position.line as usize)?;
let col = position.character as usize;
if col > line.len() {
return None;
}
let start = line[..col]
.rfind(|c: char| !c.is_alphanumeric() && c != '_')
.map(|i| i + 1)
.unwrap_or(0);
let end = line[col..]
.find(|c: char| !c.is_alphanumeric() && c != '_')
.map(|i| col + i)
.unwrap_or(line.len());
if start >= end {
return None;
}
Some(line[start..end].to_string())
}
pub fn get_qualified_name_at_position(text: &str, position: Position) -> Option<String> {
let lines: Vec<&str> = text.lines().collect();
let line = lines.get(position.line as usize)?;
let col = position.character as usize;
if col > line.len() {
return None;
}
let start = line[..col]
.rfind(|c: char| !c.is_alphanumeric() && c != '_' && c != '.')
.map(|i| i + 1)
.unwrap_or(0);
let end = line[col..]
.find(|c: char| !c.is_alphanumeric() && c != '_' && c != '.')
.map(|i| col + i)
.unwrap_or(line.len());
if start >= end {
return None;
}
let qualified = line[start..end].to_string();
let qualified = qualified.trim_matches('.');
if qualified.is_empty() {
return None;
}
Some(qualified.to_string())
}
pub fn find_function_at_cursor(text: &str, position: Position) -> Option<(String, usize)> {
let text_before = get_text_before_cursor(text, position)?;
let mut paren_depth = 0;
let mut current_arg = 0;
let mut func_start = None;
for (i, ch) in text_before.chars().rev().enumerate() {
match ch {
')' => paren_depth += 1,
'(' => {
if paren_depth == 0 {
func_start = Some(text_before.len() - i - 1);
break;
}
paren_depth -= 1;
}
',' if paren_depth == 0 => current_arg += 1,
_ => {}
}
}
let func_start = func_start?;
let before_paren = &text_before[..func_start];
let func_name: String = before_paren
.chars()
.rev()
.take_while(|c| c.is_alphanumeric() || *c == '_' || *c == '.')
.collect::<String>()
.chars()
.rev()
.collect();
if func_name.is_empty() {
None
} else {
Some((func_name, current_arg))
}
}
pub fn token_to_range(token: &Token) -> Range {
Range {
start: Position {
line: token.location.start_line.saturating_sub(1),
character: token.location.start_column.saturating_sub(1),
},
end: Position {
line: token.location.end_line.saturating_sub(1),
character: token.location.end_column.saturating_sub(1),
},
}
}
pub fn location_to_range(loc: &Location) -> Range {
Range {
start: Position {
line: loc.start_line.saturating_sub(1),
character: loc.start_column.saturating_sub(1),
},
end: Position {
line: loc.end_line.saturating_sub(1),
character: loc.end_column.saturating_sub(1),
},
}
}
pub fn validate_range(range: Range) -> Range {
if range.end.line < range.start.line
|| (range.end.line == range.start.line && range.end.character < range.start.character)
{
Range {
start: range.start,
end: range.start,
}
} else {
range
}
}
pub fn position_in_range(pos: Position, range: &Range) -> bool {
if pos.line < range.start.line || pos.line > range.end.line {
return false;
}
if pos.line == range.start.line && pos.character < range.start.character {
return false;
}
if pos.line == range.end.line && pos.character > range.end.character {
return false;
}
true
}