use std::marker::PhantomData;
use derive_where::derive_where;
use proc_macro2::{Span, TokenStream, TokenTree};
use quote::ToTokens;
use syn::{parse::ParseStream, spanned::Spanned, token::Brace, LitStr, Token};
use super::{CustomNode, Infallible, Node};
use crate::recoverable::ParseRecoverable;
#[derive_where(Clone, Debug)]
pub struct RawText<C = Infallible> {
token_stream: TokenStream,
context_span: Option<(Span, Span)>,
#[cfg(feature = "rawtext-stable-hack-module")]
recovered_text: Option<String>,
_c: PhantomData<C>,
}
impl<C> Default for RawText<C> {
fn default() -> Self {
Self {
token_stream: Default::default(),
context_span: Default::default(),
#[cfg(feature = "rawtext-stable-hack-module")]
recovered_text: Default::default(),
_c: PhantomData,
}
}
}
impl<C> RawText<C> {
pub fn convert_custom<U>(self) -> RawText<U> {
RawText {
token_stream: self.token_stream,
context_span: self.context_span,
#[cfg(feature = "rawtext-stable-hack-module")]
recovered_text: self.recovered_text,
_c: PhantomData,
}
}
pub(crate) fn set_tag_spans(&mut self, before: impl Spanned, after: impl Spanned) {
self.context_span = Some((before.span(), after.span()));
}
pub fn to_token_stream_string(&self) -> String {
self.token_stream.to_string()
}
pub fn to_source_text(&self, with_whitespaces: bool) -> Option<String> {
if with_whitespaces {
let (start, end) = self.context_span?;
let full = start.join(end)?;
let full_text = full.source_text()?;
let start_text = start.source_text()?;
let end_text = end.source_text()?;
debug_assert!(full_text.ends_with(&end_text));
debug_assert!(full_text.starts_with(&start_text));
Some(full_text[start_text.len()..(full_text.len() - end_text.len())].to_string())
} else {
self.join_spans()?.source_text()
}
}
pub fn join_spans(&self) -> Option<Span> {
let mut span: Option<Span> = None;
for tt in self.token_stream.clone().into_iter() {
let joined = if let Some(span) = span {
span.join(tt.span())?
} else {
tt.span()
};
span = Some(joined);
}
span
}
pub fn is_empty(&self) -> bool {
self.token_stream.is_empty()
}
pub(crate) fn vec_set_context(
open_tag_end: Span,
close_tag_start: Option<Span>,
mut children: Vec<Node<C>>,
) -> Vec<Node<C>>
where
C: CustomNode,
{
let spans: Vec<Span> = Some(open_tag_end)
.into_iter()
.chain(children.iter().map(|n| n.span()))
.chain(close_tag_start)
.collect();
for (spans, children) in spans.windows(3).zip(&mut children) {
if let Node::RawText(t) = children {
t.set_tag_spans(spans[0], spans[2])
}
}
children
}
pub fn to_string_best(&self) -> String {
#[cfg(feature = "rawtext-stable-hack-module")]
if let Some(recovered) = &self.recovered_text {
return recovered.clone();
}
self.to_source_text(true)
.or_else(|| self.to_source_text(false))
.unwrap_or_else(|| self.to_token_stream_string())
}
pub fn to_source_text_hack(&self) -> Option<String> {
#[cfg(feature = "rawtext-stable-hack-module")]
{
return self.recovered_text.clone();
}
#[cfg(not(feature = "rawtext-stable-hack-module"))]
None
}
#[cfg(feature = "rawtext-stable-hack-module")]
pub(crate) fn recover_space(&mut self, other: &Self) {
self.recovered_text = Some(
other
.to_source_text(self.context_span.is_some())
.expect("Cannot recover space in this context"),
)
}
#[cfg(feature = "rawtext-stable-hack-module")]
pub(crate) fn init_recover_space(&mut self, init: String) {
self.recovered_text = Some(init)
}
}
impl RawText {
pub fn is_source_text_available() -> bool {
cfg!(rstml_signal_nightly)
}
}
impl<C: CustomNode> ParseRecoverable for RawText<C> {
fn parse_recoverable(
parser: &mut crate::recoverable::RecoverableContext,
input: ParseStream,
) -> Option<Self> {
let mut token_stream = TokenStream::new();
let any_node = |input: ParseStream| {
input.peek(Token![<])
|| input.peek(Brace)
|| input.peek(LitStr)
|| C::peek_element(&input.fork())
};
while !any_node(input) && !input.is_empty() {
token_stream.extend([parser.save_diagnostics(input.parse::<TokenTree>())?])
}
Some(Self {
token_stream,
context_span: None,
#[cfg(feature = "rawtext-stable-hack-module")]
recovered_text: None,
_c: PhantomData,
})
}
}
impl<C: CustomNode> ToTokens for RawText<C> {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.token_stream.to_tokens(tokens)
}
}
impl<C: CustomNode> From<TokenStream> for RawText<C> {
fn from(token_stream: TokenStream) -> Self {
Self {
token_stream,
context_span: None,
#[cfg(feature = "rawtext-stable-hack-module")]
recovered_text: None,
_c: PhantomData,
}
}
}