1use std::{
11 fmt::{Alignment, Display, Write},
12 marker::PhantomData,
13 path::Path,
14};
15
16use super::{Change, Tagger, Text};
17use crate::{
18 buffer::PathKind,
19 form::FormId,
20 text::{AlignCenter, AlignLeft, AlignRight, FormTag, Ghost, Spacer},
21};
22
23#[derive(Clone)]
59pub struct Builder {
60 text: Text,
61 last_form: Option<(usize, FormTag)>,
62 last_align: Option<(usize, Alignment)>,
63 buffer: String,
64 last_was_empty: bool,
65 pub no_space_after_empty: bool,
67}
68
69impl Builder {
70 pub fn new() -> Self {
75 Self::default()
76 }
77
78 pub fn build(mut self) -> Text {
90 if let Some((b, id)) = self.last_form
91 && b < self.text.last_point().byte()
92 {
93 self.text.insert_tag(Tagger::basic(), b.., id);
94 }
95 if let Some((b, align)) = self.last_align
96 && b < self.text.last_point().byte()
97 {
98 match align {
99 Alignment::Center => {
100 self.text.insert_tag(Tagger::basic(), b.., AlignCenter);
101 }
102 Alignment::Right => {
103 self.text.insert_tag(Tagger::basic(), b.., AlignRight);
104 }
105 _ => {}
106 }
107 }
108
109 self.text
110 }
111
112 pub fn build_no_double_nl(self) -> Text {
122 let mut text = self.build();
123 if let Some(last_last_byte) = text.len().byte().checked_sub(2)
124 && let Some(strs) = text.strs(last_last_byte..)
125 && strs == "\n\n"
126 {
127 text.replace_range(last_last_byte..last_last_byte + 1, "");
128 }
129
130 text
131 }
132
133 pub fn push<D: Display, _T>(&mut self, part: impl AsBuilderPart<D, _T>) {
141 self.push_builder_part(part.as_builder_part());
142 }
143
144 #[doc(hidden)]
145 pub fn push_builder_part<_T>(&mut self, part: BuilderPart<impl Display, _T>) {
146 fn push_simple(builder: &mut Builder, part: BuilderPart) {
147 use Alignment::*;
148 use BuilderPart as BP;
149
150 let end = builder.text.last_point().byte();
151 let tagger = Tagger::basic();
152
153 match part {
154 BP::Text(text) => builder.push_text(text),
155 BP::Builder(other) => builder.push_builder(other),
156 BP::Path(path) => builder.push_str(path.to_string_lossy()),
157 BP::PathKind(text) => builder.push_text(&text),
158 BP::Form(tag) => {
159 let last_form = if tag == crate::form::DEFAULT_ID.to_tag(0) {
160 builder.last_form.take()
161 } else {
162 builder.last_form.replace((end, tag))
163 };
164
165 if let Some((b, tag)) = last_form
166 && b < end
167 {
168 builder.text.insert_tag(tagger, b..end, tag);
169 }
170 }
171 BP::AlignLeft => match builder.last_align.take() {
172 Some((b, Center)) if b < end => {
173 builder.text.insert_tag(tagger, b..end, AlignCenter);
174 }
175 Some((b, Right)) if b < end => {
176 builder.text.insert_tag(tagger, b..end, AlignRight);
177 }
178 _ => {}
179 },
180 BP::AlignCenter => match builder.last_align.take() {
181 Some((b, Center)) => builder.last_align = Some((b, Center)),
182 Some((b, Right)) if b < end => {
183 builder.text.insert_tag(tagger, b..end, AlignRight);
184 builder.last_align = Some((end, Center));
185 }
186 None => builder.last_align = Some((end, Center)),
187 Some(_) => {}
188 },
189 BP::AlignRight => match builder.last_align.take() {
190 Some((b, Right)) => builder.last_align = Some((b, Right)),
191 Some((b, Center)) if b < end => {
192 builder.text.insert_tag(tagger, b..end, AlignCenter);
193 builder.last_align = Some((end, Right));
194 }
195 None => builder.last_align = Some((end, Right)),
196 Some(_) => {}
197 },
198 BP::Spacer(_) => _ = builder.text.insert_tag(tagger, end, Spacer),
199 BP::Ghost(ghost) => _ = builder.text.insert_tag(tagger, end, ghost.clone()),
200 BP::ToString(_) => unsafe { std::hint::unreachable_unchecked() },
201 }
202 }
203
204 match part.try_to_basic() {
205 Ok(part_ref) => push_simple(self, part_ref),
206 Err(BuilderPart::ToString(display)) => self.push_str(display),
207 Err(_) => unsafe { std::hint::unreachable_unchecked() },
208 }
209 }
210
211 pub fn last_was_empty(&self) -> bool {
216 self.last_was_empty
217 }
218
219 pub fn push_str<D: Display>(&mut self, d: D) {
231 self.buffer.clear();
232 write!(self.buffer, "{d}").unwrap();
233 if self.buffer.is_empty()
234 || (self.no_space_after_empty && self.buffer == " " && self.last_was_empty)
235 {
236 self.last_was_empty = true;
237 } else {
238 self.last_was_empty = false;
239 let end = self.text.last_point();
240 self.text
241 .apply_change_inner(0, Change::str_insert(&self.buffer, end));
242 }
243 }
244
245 pub fn reset_form(&mut self) {
251 let end = self.text.last_point().byte();
252 if let Some((b, last_form)) = self.last_form.take() {
253 self.text.insert_tag(Tagger::basic(), b..end, last_form);
254 }
255 }
256
257 pub fn reset_alignment(&mut self) {
263 let end = self.text.last_point().byte();
264 match self.last_align.take() {
265 Some((b, Alignment::Center)) if b < end => {
266 self.text.insert_tag(Tagger::basic(), b..end, AlignCenter);
267 }
268 Some((b, Alignment::Right)) if b < end => {
269 self.text.insert_tag(Tagger::basic(), b..end, AlignRight);
270 }
271 _ => {}
272 }
273 }
274
275 fn push_text(&mut self, text: &Text) {
277 self.last_was_empty = text.is_empty();
278 self.text.insert_text(self.text.last_point(), text);
279 }
280
281 fn push_builder(&mut self, other: &Builder) {
283 self.last_was_empty = other.text.is_empty();
284
285 let offset = self.text.last_point().byte();
286 self.text.insert_text(offset, &other.text);
287 let end = self.text.last_point().byte();
288
289 if let Some((b, id)) = other.last_form
290 && b < other.text.last_point().byte()
291 {
292 self.text.insert_tag(Tagger::basic(), offset + b..end, id);
293 }
294 if let Some((b, align)) = other.last_align
295 && b < other.text.last_point().byte()
296 {
297 let tagger = Tagger::basic();
298 if let Alignment::Center = align {
299 self.text.insert_tag(tagger, offset + b..end, AlignCenter);
300 } else {
301 self.text.insert_tag(tagger, offset + b..end, AlignRight);
302 }
303 }
304 }
305}
306
307impl Default for Builder {
308 fn default() -> Self {
309 Builder {
310 text: Text::new(),
311 last_form: None,
312 last_align: None,
313 buffer: String::with_capacity(50),
314 last_was_empty: false,
315 no_space_after_empty: false,
316 }
317 }
318}
319
320impl std::fmt::Debug for Builder {
321 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322 f.debug_struct("Builder")
323 .field("text", &self.text)
324 .finish_non_exhaustive()
325 }
326}
327
328impl From<Builder> for Text {
329 fn from(value: Builder) -> Self {
330 value.build()
331 }
332}
333
334#[derive(Clone)]
336pub enum BuilderPart<'a, D: Display = String, _T = ()> {
337 Text(&'a Text),
346 Builder(&'a Builder),
352 ToString(&'a D),
354 Path(&'a std::path::Path),
356 PathKind(Text),
358 Form(FormTag),
360 AlignLeft,
362 AlignCenter,
364 AlignRight,
366 Spacer(PhantomData<_T>),
368 Ghost(&'a Ghost),
370}
371
372impl<'a, D: Display, _T> BuilderPart<'a, D, _T> {
373 fn try_to_basic(self) -> Result<BuilderPart<'a>, Self> {
374 match self {
375 BuilderPart::Text(text) => Ok(BuilderPart::Text(text)),
376 BuilderPart::Builder(builder) => Ok(BuilderPart::Builder(builder)),
377 BuilderPart::ToString(_) => Err(self),
378 BuilderPart::Path(path) => Ok(BuilderPart::Path(path)),
379 BuilderPart::PathKind(text) => Ok(BuilderPart::PathKind(text)),
380 BuilderPart::Form(form_id) => Ok(BuilderPart::Form(form_id)),
381 BuilderPart::AlignLeft => Ok(BuilderPart::AlignLeft),
382 BuilderPart::AlignCenter => Ok(BuilderPart::AlignCenter),
383 BuilderPart::AlignRight => Ok(BuilderPart::AlignRight),
384 BuilderPart::Spacer(_) => Ok(BuilderPart::Spacer(PhantomData)),
385 BuilderPart::Ghost(ghost) => Ok(BuilderPart::Ghost(ghost)),
386 }
387 }
388}
389
390pub trait AsBuilderPart<D: Display = String, _T = ()> {
397 fn as_builder_part(&self) -> BuilderPart<'_, D, _T>;
399}
400
401macro_rules! implAsBuilderPart {
402 ($type:ident, $value:ident, $result:expr) => {
403 impl AsBuilderPart for $type {
404 fn as_builder_part(&self) -> BuilderPart<'_> {
405 let $value = self;
406 $result
407 }
408 }
409 };
410}
411
412implAsBuilderPart!(Builder, builder, BuilderPart::Builder(builder));
413implAsBuilderPart!(FormId, form_id, BuilderPart::Form(form_id.to_tag(0)));
414implAsBuilderPart!(FormTag, form_tag, BuilderPart::Form(*form_tag));
415implAsBuilderPart!(AlignLeft, _align, BuilderPart::AlignLeft);
416implAsBuilderPart!(AlignCenter, _align, BuilderPart::AlignCenter);
417implAsBuilderPart!(AlignRight, _align, BuilderPart::AlignRight);
418implAsBuilderPart!(Spacer, _spacer, BuilderPart::Spacer(PhantomData));
419implAsBuilderPart!(Ghost, ghost, BuilderPart::Ghost(ghost));
420implAsBuilderPart!(Text, text, BuilderPart::Text(text));
421implAsBuilderPart!(Path, path, BuilderPart::Path(path));
422implAsBuilderPart!(PathKind, path, BuilderPart::PathKind(path.name_txt()));
423
424impl<D: Display> AsBuilderPart<D, D> for D {
425 fn as_builder_part(&self) -> BuilderPart<'_, D, D> {
426 BuilderPart::ToString(self)
427 }
428}
429
430#[macro_export]
436#[doc(hidden)]
437macro_rules! __txt__ {
438 ($($parts:tt)+) => {{
439 #[allow(unused_imports)]
440 use $crate::{
441 __parse_arg__, __parse_form__, __parse_str__, private_exports::format_like
442 };
443
444 let mut builder = $crate::text::Builder::new();
445 let _ = format_like!(
446 __parse_str__,
447 [('{', __parse_arg__, false), ('[', __parse_form__, true)],
448 &mut builder,
449 $($parts)*
450 );
451
452 builder.build()
453 }};
454}
455
456#[macro_export]
457#[doc(hidden)]
458macro_rules! __log__ {
459 ($lvl:expr, $($arg:tt)*) => {{
460 #[allow(unused_must_use)]
461 let text = $crate::text::txt!($($arg)*);
462
463 $crate::context::logs().push_record($crate::context::Record::new(
464 text,
465 $lvl,
466 ));
467 }}
468}
469
470#[macro_export]
471#[doc(hidden)]
472macro_rules! __parse_str__ {
473 ($builder:expr, $str:literal) => {{
474 let builder = $builder;
475 builder.push_str($str);
476 builder
477 }};
478}
479
480#[macro_export]
481#[doc(hidden)]
482macro_rules! __parse_arg__ {
483 ($builder:expr,"", $arg:expr) => {{
484 use $crate::text::AsBuilderPart;
485 let builder = $builder;
486 builder.push_builder_part($arg.as_builder_part());
487 builder
488 }};
489 ($builder:expr, $modif:literal, $arg:expr) => {{
490 let builder = $builder;
491 builder.push_str(format!(concat!("{:", $modif, "}"), &$arg));
492 builder
493 }};
494}
495
496#[macro_export]
497#[doc(hidden)]
498macro_rules! __parse_form__ {
499 ($builder:expr, $priority:literal,) => {{
500 use $crate::text::AsBuilderPart;
501 const PRIORITY: u8 = $crate::priority($priority);
502 let builder = $builder;
503 let id = $crate::form::DEFAULT_ID;
504 builder.push_builder_part(id.to_tag(PRIORITY).as_builder_part());
505 builder
506 }};
507 ($builder:expr, $priority:literal, a) => {{
508 use $crate::text::AsBuilderPart;
509 const PRIORITY: u8 = $crate::priority($priority);
510 let builder = $builder;
511 let id = $crate::form::ACCENT_ID;
512 builder.push_builder_part(id.to_tag(PRIORITY).as_builder_part());
513 builder
514 }};
515 ($builder:expr, $priority:literal, $($form:tt)*) => {{
516 use $crate::text::AsBuilderPart;
517 const PRIORITY: u8 = $crate::priority($priority);
518 let builder = $builder;
519 let id = $crate::form::id_of!(concat!($(stringify!($form)),*));
520 builder.push_builder_part(id.to_tag(PRIORITY).as_builder_part());
521 builder
522 }};
523}