use std::vec;
use lsp_textdocument::FullTextDocument;
use lsp_types::{Position, Range, SelectionRange};
use crate::parser::{
html_document::{HTMLDocument, Node},
html_scanner::{Scanner, ScannerState, TokenType},
};
pub fn get_selection_ranges(
document: &FullTextDocument,
positions: &Vec<Position>,
html_document: &HTMLDocument,
case_sensitive: bool,
) -> Vec<SelectionRange> {
positions
.iter()
.map(|position| get_selection_range(position, document, html_document, case_sensitive))
.collect()
}
fn get_selection_range(
position: &Position,
document: &FullTextDocument,
html_document: &HTMLDocument,
case_sensitive: bool,
) -> SelectionRange {
let applicable_ranges =
get_applicable_ranges(position, document, html_document, case_sensitive);
let mut prev: Option<(usize, usize)> = None;
let mut current: Option<Box<SelectionRange>> = None;
if applicable_ranges.len() > 0 {
let mut index = applicable_ranges.len() - 1;
loop {
let range = applicable_ranges[index];
if !prev.is_some_and(|v| range == v) {
current = Some(Box::new(SelectionRange {
range: Range::new(
document.position_at(range.0 as u32),
document.position_at(range.1 as u32),
),
parent: current,
}));
}
prev = Some(range);
if index > 0 {
index -= 1;
} else {
break;
}
}
}
if current.is_none() {
SelectionRange {
range: Range::new(*position, *position),
parent: None,
}
} else {
*current.unwrap()
}
}
fn get_applicable_ranges(
position: &Position,
document: &FullTextDocument,
html_document: &HTMLDocument,
case_sensitive: bool,
) -> Vec<(usize, usize)> {
let curr_offset = document.offset_at(*position) as usize;
let mut parent_list = vec![];
let curr_node = html_document.find_node_at(curr_offset, &mut parent_list);
let mut result = get_all_parent_tag_ranges(parent_list, html_document);
if let Some(curr_node) = curr_node {
if curr_node.start_tag_end.is_some() && curr_node.end_tag_start.is_none() {
let start_tag_end = curr_node.start_tag_end.unwrap() as u32;
if start_tag_end != curr_node.end as u32 {
return vec![(curr_node.start, curr_node.end)];
}
let close_range = Range::new(
document.position_at(start_tag_end - 2),
document.position_at(start_tag_end),
);
let close_text = document.get_content(Some(close_range));
if close_text == "/>" {
result.insert(0, (curr_node.start + 1, start_tag_end as usize - 2));
} else {
result.insert(0, (curr_node.start + 1, start_tag_end as usize - 1))
}
let mut attribute_level_ranges =
get_attribute_level_ranges(document, curr_node, curr_offset, case_sensitive);
attribute_level_ranges.append(&mut result);
result = attribute_level_ranges;
return result;
}
if curr_node.start_tag_end.is_none() || curr_node.end_tag_start.is_none() {
return result;
}
let start_tag_end = curr_node.start_tag_end.unwrap();
let end_tag_start = curr_node.end_tag_start.unwrap();
result.insert(0, (curr_node.start, curr_node.end));
if curr_node.start < curr_offset && curr_offset < start_tag_end {
result.insert(0, (curr_node.start + 1, start_tag_end - 1));
let mut attribute_level_ranges =
get_attribute_level_ranges(document, curr_node, curr_offset, case_sensitive);
attribute_level_ranges.append(&mut result);
result = attribute_level_ranges;
return result;
}
if start_tag_end <= curr_offset && curr_offset <= end_tag_start {
result.insert(0, (start_tag_end, end_tag_start));
return result;
}
if curr_offset >= end_tag_start + 2 {
result.insert(0, (end_tag_start + 2, curr_node.end - 1));
}
}
result
}
fn get_all_parent_tag_ranges(
mut parent_list: Vec<&Node>,
html_document: &HTMLDocument,
) -> Vec<(usize, usize)> {
let mut result = vec![];
while parent_list.len() > 0 {
let curr_node = parent_list.pop();
if let Some(node) = curr_node {
result.append(&mut get_node_ranges(&node));
}
}
if html_document.roots.len() > 0 {
result.push((
html_document.roots[0].start,
html_document.roots[html_document.roots.len() - 1].end,
));
}
result
}
fn get_node_ranges(node: &Node) -> Vec<(usize, usize)> {
if node.start_tag_end.is_some() && node.end_tag_start.is_some() {
let start_tag_end = node.start_tag_end.unwrap();
let end_tag_start = node.end_tag_start.unwrap();
if start_tag_end < end_tag_start {
return vec![(start_tag_end, end_tag_start), (node.start, node.end)];
}
}
vec![(node.start, node.end)]
}
fn get_attribute_level_ranges(
document: &FullTextDocument,
curr_node: &Node,
curr_offset: usize,
case_sensitive: bool,
) -> Vec<(usize, usize)> {
let curr_node_range = Range::new(
document.position_at(curr_node.start as u32),
document.position_at(curr_node.end as u32),
);
let curr_node_text = document.get_content(Some(curr_node_range));
let relative_offset = curr_offset - curr_node.start;
let mut scanner = Scanner::new(
curr_node_text,
0,
ScannerState::WithinContent,
false,
case_sensitive,
);
let mut token = scanner.scan();
let position_offset = curr_node.start;
let mut result = vec![];
let mut is_inside_attribute = false;
let mut attr_start = 0;
while token != TokenType::EOS {
match token {
TokenType::AttributeName => {
if relative_offset < scanner.get_token_offset() {
is_inside_attribute = false;
} else {
if relative_offset <= scanner.get_token_end() {
result.insert(0, (scanner.get_token_offset(), scanner.get_token_end()));
}
is_inside_attribute = true;
attr_start = scanner.get_token_offset();
}
}
TokenType::AttributeValue => {
if is_inside_attribute {
let value_text = scanner.get_token_text();
if relative_offset < scanner.get_token_offset() {
result.push((attr_start, scanner.get_token_end()));
} else if relative_offset >= scanner.get_token_offset()
&& relative_offset <= scanner.get_token_end()
{
result.insert(0, (scanner.get_token_offset(), scanner.get_token_end()));
let first_ch = value_text.get(0..1);
let end_ch = value_text.get((value_text.len() - 1)..);
if (first_ch.is_some_and(|ch| ch == r#"""#)
&& end_ch.is_some_and(|ch| ch == r#"""#))
|| (first_ch.is_some_and(|ch| ch == "'")
&& end_ch.is_some_and(|ch| ch == "'"))
{
if relative_offset >= scanner.get_token_offset() + 1
&& relative_offset <= scanner.get_token_end() - 1
{
result.insert(
0,
(scanner.get_token_offset() + 1, scanner.get_token_end() - 1),
);
}
}
result.push((attr_start, scanner.get_token_end()));
}
}
}
_ => {}
}
token = scanner.scan();
}
result
.iter()
.map(|pair| (pair.0 + position_offset, pair.1 + position_offset))
.collect()
}