use super::{BEGIN_CMD, END_CMD, Parser};
use crate::syntax::SyntaxKind;
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum CommentMode {
Skip,
Stop,
}
pub(super) const BLANK_LINE_NEWLINES: usize = 2;
pub(super) struct TriviaScan {
pub(super) next: usize,
pub(super) next_kind: Option<SyntaxKind>,
pub(super) saw_blank_line: bool,
pub(super) saw_blank_line_outside_guards: bool,
pub(super) comment_start: Option<usize>,
}
impl Parser<'_> {
pub(super) fn is_trivia(k: SyntaxKind) -> bool {
matches!(
k,
SyntaxKind::WHITESPACE
| SyntaxKind::NEWLINE
| SyntaxKind::COMMENT
| SyntaxKind::DOC_MARGIN
| SyntaxKind::GUARD
)
}
pub(super) fn skip_trivia(&mut self) {
while self.kind().is_some_and(Self::is_trivia) {
self.bump();
}
}
pub(super) fn scan_trivia(&self, from: usize, comment_mode: CommentMode) -> TriviaScan {
let mut i = from;
let mut newlines = 0;
let mut guard_newlines = 0;
let mut saw_blank_line = false;
let mut saw_blank_line_outside_guards = false;
let mut comment_start = None;
while let Some(t) = self.tokens.get(i) {
match t.kind {
SyntaxKind::NEWLINE => {
newlines += 1;
guard_newlines += 1;
if newlines >= BLANK_LINE_NEWLINES {
saw_blank_line = true;
comment_start = None;
}
if guard_newlines >= BLANK_LINE_NEWLINES {
saw_blank_line_outside_guards = true;
}
}
SyntaxKind::WHITESPACE | SyntaxKind::DOC_MARGIN => {}
SyntaxKind::GUARD => guard_newlines = 0,
SyntaxKind::COMMENT if comment_mode == CommentMode::Stop => break,
SyntaxKind::COMMENT => {
newlines = 0;
guard_newlines = 0;
if comment_start.is_none() && self.comment_starts_line(i) {
comment_start = Some(i);
}
}
_ => break,
}
i += 1;
}
TriviaScan {
next: i,
next_kind: self.tokens.get(i).map(|t| t.kind),
saw_blank_line,
saw_blank_line_outside_guards,
comment_start,
}
}
pub(super) fn peek_meaningful(&self) -> (Option<SyntaxKind>, bool) {
let s = self.scan_trivia(self.pos, CommentMode::Skip);
(s.next_kind, s.saw_blank_line)
}
pub(super) fn peek_meaningful_text(&self) -> Option<&str> {
let mut i = self.pos;
while let Some(t) = self.tokens.get(i) {
if !Self::is_trivia(t.kind) {
return Some(t.text.as_str());
}
i += 1;
}
None
}
pub(super) fn at_paragraph_break(&self) -> bool {
self.scan_trivia(self.pos, CommentMode::Skip).saw_blank_line
}
pub(super) fn at_paragraph_break_outside_guards(&self) -> bool {
self.scan_trivia(self.pos, CommentMode::Skip)
.saw_blank_line_outside_guards
}
pub(super) fn comment_starts_line(&self, pos: usize) -> bool {
let mut i = pos;
while i > 0 {
i -= 1;
match self.tokens[i].kind {
SyntaxKind::WHITESPACE | SyntaxKind::DOC_MARGIN | SyntaxKind::GUARD => {
continue;
}
SyntaxKind::NEWLINE => return true,
_ => return false,
}
}
true
}
pub(super) fn binding_run(&self, from: usize) -> Option<(usize, usize, SyntaxKind)> {
let s = self.scan_trivia(from, CommentMode::Skip);
let start = s.comment_start?;
if s.next_kind != Some(SyntaxKind::CONTROL_WORD) {
return None;
}
if self.alias_end.is_some_and(|end| s.next >= end) {
return None;
}
let kind = match self.tokens[s.next].text.as_str() {
BEGIN_CMD => SyntaxKind::ENVIRONMENT,
END_CMD => return None,
_ => SyntaxKind::COMMAND,
};
Some((start, s.next, kind))
}
}