use ktrs_ast::{Ast, NodeId};
use ktrs_syntax::SyntaxKind::EOL_COMMENT;
use super::{AstNodeExtension, indent_internal};
pub trait AstNodeLines {
fn column(&self, n: NodeId) -> usize;
fn indent_without_newline_prefix(&self, n: NodeId) -> String;
fn has_new_line_in_closed_range(&self, from: NodeId, to: NodeId) -> bool;
fn no_new_line_in_closed_range(&self, from: NodeId, to: NodeId) -> bool;
fn no_new_line_in_open_range(&self, from: NodeId, to: NodeId) -> bool;
fn leaves_in_open_range(&self, from: NodeId, to: NodeId) -> impl Iterator<Item = NodeId> + '_;
fn leaves_in_closed_range(&self, from: NodeId, to: NodeId) -> impl Iterator<Item = NodeId> + '_;
fn leaves_forwards_including_self(&self, n: NodeId) -> impl Iterator<Item = NodeId> + '_;
fn leaves_backwards_including_self(&self, n: NodeId) -> impl Iterator<Item = NodeId> + '_;
fn leaves_on_line(&self, n: NodeId) -> impl Iterator<Item = NodeId> + '_;
fn drop_trailing_eol_comment<'a>(&'a self, leaves: impl Iterator<Item = NodeId> + 'a) -> impl Iterator<Item = NodeId> + 'a;
fn first_leaf_on_line_or_self(&self, n: NodeId) -> NodeId;
fn last_leaf_on_line_or_null(&self, n: NodeId) -> Option<NodeId>;
fn line_length(&self, leaves: impl IntoIterator<Item = NodeId>) -> usize;
}
impl AstNodeLines for Ast {
fn column(&self, n: NodeId) -> usize {
let mut leaf = self.prev_leaf(n);
let mut offset_to_the_left = 0;
while let Some(l) = leaf {
if self.is_white_space_with_newline(l) {
let text = self.leaf_text(l);
let after_last_newline = &text[text.rfind('\n').map_or(0, |i| i + 1)..];
offset_to_the_left += after_last_newline.encode_utf16().count();
break;
}
offset_to_the_left += self.text_length_utf16(l);
leaf = self.prev_leaf(l);
}
offset_to_the_left + 1
}
fn indent_without_newline_prefix(&self, n: NodeId) -> String {
let indent = indent_internal(self, n);
indent.strip_prefix('\n').unwrap_or(indent).to_owned()
}
fn has_new_line_in_closed_range(&self, from: NodeId, to: NodeId) -> bool {
self.leaves_in_closed_range(from, to).any(|it| self.text_contains(it, '\n'))
}
fn no_new_line_in_closed_range(&self, from: NodeId, to: NodeId) -> bool {
!self.leaves_in_closed_range(from, to).any(|it| self.text_contains(it, '\n'))
}
fn no_new_line_in_open_range(&self, from: NodeId, to: NodeId) -> bool {
!self.leaves_in_open_range(from, to).any(|it| self.text_contains(it, '\n'))
}
fn leaves_in_open_range(&self, from: NodeId, to: NodeId) -> impl Iterator<Item = NodeId> + '_ {
let to_last_leaf = self.last_child_leaf_or_self(to);
self.leaves(from, true).take_while(move |&it| it != to && it != to_last_leaf)
}
fn leaves_in_closed_range(&self, from: NodeId, to: NodeId) -> impl Iterator<Item = NodeId> + '_ {
let stop_at_leaf = self.next_leaf(self.last_child_leaf_or_self(to));
self.leaves_forwards_including_self(self.first_child_leaf_or_self(from)).take_while(move |&it| Some(it) != stop_at_leaf)
}
fn leaves_forwards_including_self(&self, n: NodeId) -> impl Iterator<Item = NodeId> + '_ {
self.is_leaf(n).then_some(n).into_iter().chain(self.leaves(n, true))
}
fn leaves_backwards_including_self(&self, n: NodeId) -> impl Iterator<Item = NodeId> + '_ {
self.is_leaf(n).then_some(n).into_iter().chain(self.leaves(n, false))
}
fn leaves_on_line(&self, n: NodeId) -> impl Iterator<Item = NodeId> + '_ {
let last_leaf_on_line_or_null = self.last_leaf_on_line_or_null(n);
let take_all = last_leaf_on_line_or_null.is_none();
self.leaves_forwards_including_self(self.first_leaf_on_line_or_self(n))
.take_while(move |&it| take_all || self.prev_leaf(it) != last_leaf_on_line_or_null)
}
fn drop_trailing_eol_comment<'a>(&'a self, leaves: impl Iterator<Item = NodeId> + 'a) -> impl Iterator<Item = NodeId> + 'a {
leaves.take_while(move |&it| {
!(self.is_white_space_without_newline(it) && self.next_leaf(it).is_some_and(|n| self.element_type(n) == EOL_COMMENT))
&& self.element_type(it) != EOL_COMMENT
})
}
fn first_leaf_on_line_or_self(&self, n: NodeId) -> NodeId {
self.prev_leaf_matching(n, |it| (self.text_contains(it, '\n') && !self.is_part_of_comment(it)) || self.prev_leaf(it).is_none())
.unwrap_or(n)
}
fn last_leaf_on_line_or_null(&self, n: NodeId) -> Option<NodeId> {
self.next_leaf_matching(n, |it| self.text_contains(it, '\n')).and_then(|it| self.prev_leaf(it))
}
fn line_length(&self, leaves: impl IntoIterator<Item = NodeId>) -> usize {
let mut leaves = leaves.into_iter().peekable();
let Some(&first) = leaves.peek() else { return 0 };
assert!(
self.text_contains(first, '\n') || self.prev_leaf(first).is_none(),
"IllegalArgumentException: First node in non-empty sequence must be a whitespace containing a newline"
);
let (mut leading, mut length) = (true, 0);
for leaf in leaves {
for c in self.text_chunks(leaf).flat_map(str::chars) {
match c {
'\n' if leading => {}
'\n' => return length,
_ => {
leading = false;
length += c.len_utf16();
}
}
}
}
length
}
}