1use std::{
11 fmt::{Alignment, Display, Write},
12 marker::PhantomData,
13 path::PathBuf,
14};
15
16use super::{Change, Tagger, Text};
17use crate::{
18 form::FormId,
19 text::{AlignCenter, AlignLeft, AlignRight, FormTag, Ghost, Spacer},
20};
21
22#[derive(Clone)]
58pub struct Builder {
59 text: Text,
60 last_form: Option<(usize, FormTag)>,
61 last_align: Option<(usize, Alignment)>,
62 buffer: String,
63 last_was_empty: bool,
64 pub no_space_after_empty: bool,
66}
67
68impl Builder {
69 pub fn new() -> Self {
74 Self::default()
75 }
76
77 pub fn build(mut self) -> Text {
89 if let Some((b, id)) = self.last_form
90 && b < self.text.last_point().byte()
91 {
92 self.text.insert_tag(Tagger::basic(), b.., id);
93 }
94 if let Some((b, align)) = self.last_align
95 && b < self.text.last_point().byte()
96 {
97 match align {
98 Alignment::Center => {
99 self.text.insert_tag(Tagger::basic(), b.., AlignCenter);
100 }
101 Alignment::Right => {
102 self.text.insert_tag(Tagger::basic(), b.., AlignRight);
103 }
104 _ => {}
105 }
106 }
107
108 self.text
109 }
110
111 pub fn push<D: Display, _T>(&mut self, part: impl Into<BuilderPart<D, _T>>) {
119 self.push_builder_part(part.into());
120 }
121
122 #[doc(hidden)]
123 pub fn push_builder_part<_T>(&mut self, part: BuilderPart<impl Display, _T>) {
124 fn push_basic(builder: &mut Builder, part: BuilderPart) {
125 use Alignment::*;
126 use BuilderPart as BP;
127
128 let end = builder.text.last_point().byte();
129 let tagger = Tagger::basic();
130
131 match part {
132 BP::Text(text) => builder.push_text(text),
133 BP::Builder(new) => builder.push_text(new.build()),
134 BP::Form(tag) => {
135 let last_form = if tag == crate::form::DEFAULT_ID.to_tag(0) {
136 builder.last_form.take()
137 } else {
138 builder.last_form.replace((end, tag))
139 };
140
141 if let Some((b, tag)) = last_form
142 && b < end
143 {
144 builder.text.insert_tag(tagger, b..end, tag);
145 }
146 }
147 BP::AlignLeft => match builder.last_align.take() {
148 Some((b, Center)) if b < end => {
149 builder.text.insert_tag(tagger, b..end, AlignCenter);
150 }
151 Some((b, Right)) if b < end => {
152 builder.text.insert_tag(tagger, b..end, AlignRight);
153 }
154 _ => {}
155 },
156 BP::AlignCenter => match builder.last_align.take() {
157 Some((b, Center)) => builder.last_align = Some((b, Center)),
158 Some((b, Right)) if b < end => {
159 builder.text.insert_tag(tagger, b..end, AlignRight);
160 builder.last_align = Some((end, Center));
161 }
162 None => builder.last_align = Some((end, Center)),
163 Some(_) => {}
164 },
165 BP::AlignRight => match builder.last_align.take() {
166 Some((b, Right)) => builder.last_align = Some((b, Right)),
167 Some((b, Center)) if b < end => {
168 builder.text.insert_tag(tagger, b..end, AlignCenter);
169 builder.last_align = Some((end, Right));
170 }
171 None => builder.last_align = Some((end, Right)),
172 Some(_) => {}
173 },
174 BP::Spacer(_) => {
175 builder.text.insert_tag(tagger, end, Spacer);
176 }
177 BP::Ghost(text) => {
178 builder.text.insert_tag(tagger, end, Ghost(text));
179 }
180 BP::ToString(_) => unsafe { std::hint::unreachable_unchecked() },
181 }
182 }
183
184 match part.try_to_basic() {
185 Ok(basic_part) => push_basic(self, basic_part),
186 Err(BuilderPart::ToString(display)) => self.push_str(display),
187 Err(_) => unsafe { std::hint::unreachable_unchecked() },
188 }
189 }
190
191 pub fn last_was_empty(&self) -> bool {
196 self.last_was_empty
197 }
198
199 pub fn push_str<D: Display>(&mut self, d: D) {
211 self.buffer.clear();
212 write!(self.buffer, "{d}").unwrap();
213 if self.buffer.is_empty()
214 || (self.no_space_after_empty && self.buffer == " " && self.last_was_empty)
215 {
216 self.last_was_empty = true;
217 } else {
218 self.last_was_empty = false;
219 let end = self.text.last_point();
220 self.text
221 .apply_change_inner(0, Change::str_insert(&self.buffer, end));
222 }
223 }
224
225 pub fn reset_form(&mut self) {
231 let end = self.text.last_point().byte();
232 if let Some((b, last_form)) = self.last_form.take() {
233 self.text.insert_tag(Tagger::basic(), b..end, last_form);
234 }
235 }
236
237 pub fn reset_alignment(&mut self) {
243 let end = self.text.last_point().byte();
244 match self.last_align.take() {
245 Some((b, Alignment::Center)) if b < end => {
246 self.text.insert_tag(Tagger::basic(), b..end, AlignCenter);
247 }
248 Some((b, Alignment::Right)) if b < end => {
249 self.text.insert_tag(Tagger::basic(), b..end, AlignRight);
250 }
251 _ => {}
252 }
253 }
254
255 fn push_text(&mut self, text: Text) {
257 self.last_was_empty = text.is_empty();
258 self.text.insert_text(self.text.last_point(), text);
259 }
260}
261
262impl Default for Builder {
263 fn default() -> Self {
264 Builder {
265 text: Text::new(),
266 last_form: None,
267 last_align: None,
268 buffer: String::with_capacity(50),
269 last_was_empty: false,
270 no_space_after_empty: false,
271 }
272 }
273}
274
275impl std::fmt::Debug for Builder {
276 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277 f.debug_struct("Builder")
278 .field("text", &self.text)
279 .finish_non_exhaustive()
280 }
281}
282
283impl From<Builder> for Text {
284 fn from(value: Builder) -> Self {
285 value.build()
286 }
287}
288
289#[derive(Clone)]
291pub enum BuilderPart<D: Display = String, _T = ()> {
292 Text(Text),
301 Builder(Builder),
307 ToString(D),
309 Form(FormTag),
311 AlignLeft,
313 AlignCenter,
315 AlignRight,
317 Spacer(PhantomData<_T>),
319 Ghost(Text),
321}
322
323impl<D: Display, _T> BuilderPart<D, _T> {
324 fn try_to_basic(self) -> Result<BuilderPart, Self> {
325 match self {
326 BuilderPart::Text(text) => Ok(BuilderPart::Text(text)),
327 BuilderPart::Builder(builder) => Ok(BuilderPart::Builder(builder)),
328 BuilderPart::ToString(d) => Err(BuilderPart::ToString(d)),
329 BuilderPart::Form(form_id) => Ok(BuilderPart::Form(form_id)),
330 BuilderPart::AlignLeft => Ok(BuilderPart::AlignLeft),
331 BuilderPart::AlignCenter => Ok(BuilderPart::AlignCenter),
332 BuilderPart::AlignRight => Ok(BuilderPart::AlignRight),
333 BuilderPart::Spacer(_) => Ok(BuilderPart::Spacer(PhantomData)),
334 BuilderPart::Ghost(text) => Ok(BuilderPart::Ghost(text)),
335 }
336 }
337}
338
339impl From<Builder> for BuilderPart {
340 fn from(value: Builder) -> Self {
341 Self::Builder(value)
342 }
343}
344
345impl From<FormId> for BuilderPart {
346 fn from(value: FormId) -> Self {
347 Self::Form(value.to_tag(0))
348 }
349}
350
351impl From<FormTag> for BuilderPart {
352 fn from(value: FormTag) -> Self {
353 Self::Form(value)
354 }
355}
356
357impl From<AlignLeft> for BuilderPart {
358 fn from(_: AlignLeft) -> Self {
359 Self::AlignLeft
360 }
361}
362
363impl From<AlignCenter> for BuilderPart {
364 fn from(_: AlignCenter) -> Self {
365 Self::AlignCenter
366 }
367}
368
369impl From<AlignRight> for BuilderPart {
370 fn from(_: AlignRight) -> Self {
371 Self::AlignRight
372 }
373}
374
375impl From<Spacer> for BuilderPart {
376 fn from(_: Spacer) -> Self {
377 Self::Spacer(PhantomData)
378 }
379}
380
381impl<T: Into<Text>> From<Ghost<T>> for BuilderPart {
382 fn from(value: Ghost<T>) -> Self {
383 Self::Ghost(value.0.into())
384 }
385}
386
387impl From<Text> for BuilderPart {
388 fn from(value: Text) -> Self {
389 Self::Text(value)
390 }
391}
392
393impl<D: Display> From<D> for BuilderPart<D, D> {
394 fn from(value: D) -> Self {
395 Self::ToString(value)
396 }
397}
398
399impl From<PathBuf> for BuilderPart {
400 fn from(value: PathBuf) -> Self {
401 Self::ToString(value.to_string_lossy().to_string())
402 }
403}
404
405impl<'a> From<&'a PathBuf> for BuilderPart<std::borrow::Cow<'a, str>> {
406 fn from(value: &'a PathBuf) -> Self {
407 Self::ToString(value.to_string_lossy())
408 }
409}
410
411impl<'a> From<&'a std::path::Path> for BuilderPart<std::borrow::Cow<'a, str>> {
412 fn from(value: &'a std::path::Path) -> Self {
413 Self::ToString(value.to_string_lossy())
414 }
415}
416
417#[macro_export]
423#[doc(hidden)]
424macro_rules! __txt__ {
425 ($($parts:tt)+) => {{
426 #[allow(unused_imports)]
427 use $crate::{__parse_arg__, __parse_form__, __parse_str__, private_exports::format_like};
428
429 let mut builder = $crate::text::Builder::new();
430
431 format_like!(
432 __parse_str__,
433 [('{', __parse_arg__, false), ('[', __parse_form__, true)],
434 &mut builder,
435 $($parts)*
436 );
437
438 builder.build()
439 }};
440}
441
442#[macro_export]
443#[doc(hidden)]
444macro_rules! __log__ {
445 ($lvl:expr, $($arg:tt)*) => {{
446 #[allow(unused_must_use)]
447 let text = $crate::text::txt!($($arg)*);
448
449 $crate::context::logs().push_record($crate::context::Record::new(
450 text,
451 $lvl,
452 ));
453 }}
454}
455
456#[macro_export]
457#[doc(hidden)]
458macro_rules! __parse_str__ {
459 ($builder:expr, $str:literal) => {{
460 let builder = $builder;
461 builder.push_str($str);
462 builder
463 }};
464}
465
466#[macro_export]
467#[doc(hidden)]
468macro_rules! __parse_arg__ {
469 ($builder:expr,"", $arg:expr) => {{
470 let builder = $builder;
471 builder.push_builder_part($arg.into());
472 builder
473 }};
474 ($builder:expr, $modif:literal, $arg:expr) => {{
475 let builder = $builder;
476 builder.push_str(format!(concat!("{:", $modif, "}"), &$arg));
477 builder
478 }};
479}
480
481#[macro_export]
482#[doc(hidden)]
483macro_rules! __parse_form__ {
484 ($builder:expr, $priority:literal,) => {{
485 const PRIORITY: u8 = $crate::priority($priority);
486 let builder = $builder;
487 let id = $crate::form::DEFAULT_ID;
488 builder.push_builder_part(id.to_tag(PRIORITY).into());
489 builder
490 }};
491 ($builder:expr, $priority:literal, a) => {{
492 const PRIORITY: u8 = $crate::priority($priority);
493 let builder = $builder;
494 let id = $crate::form::ACCENT_ID;
495 builder.push_builder_part(id.to_tag(PRIORITY).into());
496 builder
497 }};
498 ($builder:expr, $priority:literal, $($form:tt)*) => {{
499 const PRIORITY: u8 = $crate::priority($priority);
500 let builder = $builder;
501 let id = $crate::form::id_of!(concat!($(stringify!($form)),*));
502 builder.push_builder_part(id.to_tag(PRIORITY).into());
503 builder
504 }};
505}