use tower_lsp::lsp_types::Position;
use crate::Backend;
impl Backend {
pub(in crate::definition) fn find_object_shape_property_position(
content: &str,
key_name: &str,
near_offset: Option<usize>,
) -> Option<Position> {
let mut matches: Vec<(usize, u32, u32)> = Vec::new(); let mut byte_offset: usize = 0;
let mut in_docblock = false;
for (line_idx, line) in content.lines().enumerate() {
let line_len = line.len() + 1;
if line.contains("/**") {
in_docblock = true;
}
if in_docblock {
if let Some(pos) = Self::find_shape_key_in_line(line, key_name) {
let abs_offset = byte_offset + pos;
matches.push((abs_offset, line_idx as u32, pos as u32));
}
}
if line.contains("*/") {
in_docblock = false;
}
byte_offset += line_len;
}
let best = match near_offset {
Some(cursor) => matches
.into_iter()
.min_by_key(|(off, _, _)| cursor.abs_diff(*off)),
None => matches.into_iter().last(),
};
best.map(|(_, line, col)| Position {
line,
character: col,
})
}
fn find_shape_key_in_line(line: &str, key_name: &str) -> Option<usize> {
let bytes = line.as_bytes();
let lower = line.to_ascii_lowercase();
let mut search_from = 0usize;
while let Some(obj_pos) = lower[search_from..].find("object{") {
let abs_obj = search_from + obj_pos;
let brace_start = abs_obj + "object".len();
let mut depth = 0i32;
let mut i = brace_start;
while i < bytes.len() {
match bytes[i] {
b'{' => depth += 1,
b'}' => {
depth -= 1;
if depth == 0 {
break;
}
}
_ if depth == 1 => {
if let Some(col) = Self::match_shape_key_at(line, i, key_name) {
return Some(col);
}
}
_ => {}
}
i += 1;
}
search_from = abs_obj + 1;
}
None
}
fn match_shape_key_at(line: &str, pos: usize, key_name: &str) -> Option<usize> {
let rest = &line[pos..];
let rest_trimmed = rest.trim_start();
let leading_ws = rest.len() - rest_trimmed.len();
let col_base = pos + leading_ws;
if let Some(after) = rest_trimmed.strip_prefix(key_name)
&& (after.starts_with(':') || after.starts_with("?:"))
{
return Some(col_base);
}
if let Some(inner) = rest_trimmed.strip_prefix('\'')
&& let Some(after_key) = inner.strip_prefix(key_name)
&& (after_key.starts_with("':") || after_key.starts_with("'?:"))
{
return Some(col_base + 1);
}
if let Some(inner) = rest_trimmed.strip_prefix('"')
&& let Some(after_key) = inner.strip_prefix(key_name)
&& (after_key.starts_with("\":") || after_key.starts_with("\"?:"))
{
return Some(col_base + 1);
}
None
}
pub(in crate::definition) fn find_eloquent_array_entry(
content: &str,
member_name: &str,
class_range: Option<(usize, usize)>,
) -> Option<Position> {
let single_pattern = format!("'{member_name}'");
let double_pattern = format!("\"{member_name}\"");
let targets = [
"$casts",
"$attributes",
"$fillable",
"$guarded",
"$hidden",
"$visible",
"$appends",
];
let mut in_target_property = false;
let mut byte_offset: usize = 0;
for (line_idx, line) in content.lines().enumerate() {
let line_len = line.len() + 1;
let in_range = match class_range {
Some((start, end)) => byte_offset >= start && byte_offset < end,
None => true,
};
if in_range {
let trimmed = line.trim();
if targets.iter().any(|t| trimmed.contains(t)) {
in_target_property = true;
}
if trimmed.contains("function casts(") {
in_target_property = true;
}
if in_target_property {
if let Some(col) = line.find(&single_pattern) {
return Some(Position {
line: line_idx as u32,
character: (col + 1) as u32,
});
}
if let Some(col) = line.find(&double_pattern) {
return Some(Position {
line: line_idx as u32,
character: (col + 1) as u32,
});
}
if trimmed == "];" || trimmed.ends_with("];") {
in_target_property = false;
}
}
}
byte_offset += line_len;
}
None
}
}