1use std::marker::PhantomData;
2
3use derive_where::derive_where;
4use proc_macro2::{Span, TokenStream, TokenTree};
5use quote::ToTokens;
6use syn::{parse::ParseStream, spanned::Spanned, token::Brace, LitStr, Token};
7
8use super::{CustomNode, Infallible, Node};
9use crate::recoverable::ParseRecoverable;
10
11#[derive_where(Clone, Debug)]
28pub struct RawText<C = Infallible> {
29 token_stream: TokenStream,
30 context_span: Option<(Span, Span)>,
32 #[cfg(feature = "rawtext-stable-hack-module")]
33 recovered_text: Option<String>,
34 _c: PhantomData<C>,
36}
37
38impl<C> Default for RawText<C> {
39 fn default() -> Self {
40 Self {
41 token_stream: Default::default(),
42 context_span: Default::default(),
43 #[cfg(feature = "rawtext-stable-hack-module")]
44 recovered_text: Default::default(),
45 _c: PhantomData,
46 }
47 }
48}
49
50impl<C> RawText<C> {
51 pub fn convert_custom<U>(self) -> RawText<U> {
54 RawText {
55 token_stream: self.token_stream,
56 context_span: self.context_span,
57 #[cfg(feature = "rawtext-stable-hack-module")]
58 recovered_text: self.recovered_text,
59 _c: PhantomData,
60 }
61 }
62 pub(crate) fn set_tag_spans(&mut self, before: impl Spanned, after: impl Spanned) {
63 self.context_span = Some((before.span(), after.span()));
66 }
67
68 pub fn to_token_stream_string(&self) -> String {
70 self.token_stream.to_string()
71 }
72
73 pub fn to_source_text(&self, with_whitespaces: bool) -> Option<String> {
81 if with_whitespaces {
82 let (start, end) = self.context_span?;
83 let full = start.join(end)?;
84 let full_text = full.source_text()?;
85 let start_text = start.source_text()?;
86 let end_text = end.source_text()?;
87 debug_assert!(full_text.ends_with(&end_text));
88 debug_assert!(full_text.starts_with(&start_text));
89 Some(full_text[start_text.len()..(full_text.len() - end_text.len())].to_string())
90 } else {
91 self.join_spans()?.source_text()
92 }
93 }
94
95 pub fn join_spans(&self) -> Option<Span> {
98 let mut span: Option<Span> = None;
99 for tt in self.token_stream.clone().into_iter() {
100 let joined = if let Some(span) = span {
101 span.join(tt.span())?
102 } else {
103 tt.span()
104 };
105 span = Some(joined);
106 }
107 span
108 }
109
110 pub fn is_empty(&self) -> bool {
111 self.token_stream.is_empty()
112 }
113
114 pub(crate) fn vec_set_context(
115 open_tag_end: Span,
116 close_tag_start: Option<Span>,
117 mut children: Vec<Node<C>>,
118 ) -> Vec<Node<C>>
119 where
120 C: CustomNode,
121 {
122 let spans: Vec<Span> = Some(open_tag_end)
123 .into_iter()
124 .chain(children.iter().map(|n| n.span()))
125 .chain(close_tag_start)
126 .collect();
127
128 for (spans, children) in spans.windows(3).zip(&mut children) {
129 if let Node::RawText(t) = children {
130 t.set_tag_spans(spans[0], spans[2])
131 }
132 }
133 children
134 }
135
136 pub fn to_string_best(&self) -> String {
142 #[cfg(feature = "rawtext-stable-hack-module")]
143 if let Some(recovered) = &self.recovered_text {
144 return recovered.clone();
145 }
146 self.to_source_text(true)
147 .or_else(|| self.to_source_text(false))
148 .unwrap_or_else(|| self.to_token_stream_string())
149 }
150
151 pub fn to_source_text_hack(&self) -> Option<String> {
156 #[cfg(feature = "rawtext-stable-hack-module")]
157 {
158 return self.recovered_text.clone();
159 }
160 #[cfg(not(feature = "rawtext-stable-hack-module"))]
161 None
162 }
163
164 #[cfg(feature = "rawtext-stable-hack-module")]
165 pub(crate) fn recover_space(&mut self, other: &Self) {
166 self.recovered_text = Some(
167 other
168 .to_source_text(self.context_span.is_some())
169 .expect("Cannot recover space in this context"),
170 )
171 }
172 #[cfg(feature = "rawtext-stable-hack-module")]
173 pub(crate) fn init_recover_space(&mut self, init: String) {
174 self.recovered_text = Some(init)
175 }
176}
177
178impl RawText {
179 pub fn is_source_text_available() -> bool {
181 cfg!(rstml_signal_nightly)
183 }
184}
185
186impl<C: CustomNode> ParseRecoverable for RawText<C> {
187 fn parse_recoverable(
188 parser: &mut crate::recoverable::RecoverableContext,
189 input: ParseStream,
190 ) -> Option<Self> {
191 let mut token_stream = TokenStream::new();
192 let any_node = |input: ParseStream| {
193 input.peek(Token![<])
194 || input.peek(Brace)
195 || input.peek(LitStr)
196 || C::peek_element(&input.fork())
197 };
198 while !any_node(input) && !input.is_empty() {
201 token_stream.extend([parser.save_diagnostics(input.parse::<TokenTree>())?])
202 }
203 Some(Self {
204 token_stream,
205 context_span: None,
206 #[cfg(feature = "rawtext-stable-hack-module")]
207 recovered_text: None,
208 _c: PhantomData,
209 })
210 }
211}
212
213impl<C: CustomNode> ToTokens for RawText<C> {
214 fn to_tokens(&self, tokens: &mut TokenStream) {
215 self.token_stream.to_tokens(tokens)
216 }
217}
218
219impl<C: CustomNode> From<TokenStream> for RawText<C> {
220 fn from(token_stream: TokenStream) -> Self {
221 Self {
222 token_stream,
223 context_span: None,
224 #[cfg(feature = "rawtext-stable-hack-module")]
225 recovered_text: None,
226 _c: PhantomData,
227 }
228 }
229}