use std::{
fmt::{Display, Write},
marker::PhantomData,
path::Path,
};
use super::{Change, Ns, Text};
use crate::{
buffer::PathKind,
form::FormId,
text::{FormTag, Inlay, Mask, Spacer},
};
#[derive(Clone)]
pub struct Builder {
text: Text,
last_form: Option<(usize, FormTag)>,
last_mask: Option<(usize, Mask)>,
buffer: String,
last_was_empty: bool,
pub no_space_after_empty: bool,
}
impl Builder {
pub fn new() -> Self {
Self::default()
}
pub fn build(mut self) -> Text {
if let Some((b, id)) = self.last_form
&& b < self.text.last_point().byte()
{
self.text.insert_tag(Ns::basic(), b.., id);
}
if let Some((b, id)) = self.last_mask
&& b < self.text.last_point().byte()
{
self.text.insert_tag(Ns::basic(), b.., id);
}
self.text
}
pub fn build_no_double_nl(self) -> Text {
let mut text = self.build();
if let Some(last_last_byte) = text.len().checked_sub(2)
&& let Some(strs) = text.get(last_last_byte..)
&& strs == "\n\n"
{
text.replace_range(last_last_byte..last_last_byte + 1, "");
}
text
}
pub fn push<D: Display, _T>(&mut self, part: impl AsBuilderPart<D, _T>) {
self.push_builder_part(part.as_builder_part());
}
#[doc(hidden)]
pub fn push_builder_part<_T>(&mut self, part: BuilderPart<impl Display, _T>) {
fn push_simple(builder: &mut Builder, part: BuilderPart) {
use BuilderPart as BP;
let end = builder.text.last_point().byte();
let ns = Ns::basic();
match part {
BP::Text(text) => builder.push_text(text),
BP::Builder(other) => builder.push_builder(other),
BP::Path(path) => builder.push_str(path.to_string_lossy()),
BP::PathKind(text) => builder.push_text(&text),
BP::Form(tag) => {
let last_form = if tag == crate::form::DEFAULT_ID.to_tag(0) {
builder.last_form.take()
} else {
builder.last_form.replace((end, tag))
};
if let Some((b, tag)) = last_form
&& b < end
{
builder.text.insert_tag(ns, b..end, tag);
}
}
BP::Spacer(_) => builder.text.insert_tag(ns, end, Spacer),
BP::Inlay(ghost) => builder.text.insert_tag(ns, end, ghost.clone()),
BP::ToString(_) => unsafe { std::hint::unreachable_unchecked() },
BP::Mask(mask) => {
let last_form = if mask == Mask::no_mask() {
builder.last_mask.take()
} else {
builder.last_mask.replace((end, mask))
};
if let Some((b, tag)) = last_form
&& b < end
{
builder.text.insert_tag(ns, b..end, tag);
}
}
}
}
match part.try_to_basic() {
Ok(part_ref) => push_simple(self, part_ref),
Err(BuilderPart::ToString(display)) => self.push_str(display),
Err(_) => unsafe { std::hint::unreachable_unchecked() },
}
}
pub fn last_was_empty(&self) -> bool {
self.last_was_empty
}
pub fn push_str<D: Display>(&mut self, d: D) {
self.buffer.clear();
write!(self.buffer, "{d}").unwrap();
if self.buffer.is_empty()
|| (self.no_space_after_empty && self.buffer == " " && self.last_was_empty)
{
self.last_was_empty = true;
} else {
self.last_was_empty = false;
let end = self.text.last_point();
self.text
.apply_change(0, Change::str_insert(&self.buffer, end));
}
}
pub fn reset_form(&mut self) {
let end = self.text.last_point().byte();
if let Some((b, last_form)) = self.last_form.take() {
self.text.insert_tag(Ns::basic(), b..end, last_form);
}
}
fn push_text(&mut self, text: &Text) {
self.last_was_empty = text.is_empty();
self.text.insert_text(self.text.last_point(), text);
}
fn push_builder(&mut self, other: &Builder) {
self.last_was_empty = other.text.is_empty();
let offset = self.text.last_point().byte();
self.text.insert_text(offset, &other.text);
let end = self.text.last_point().byte();
if let Some((b, id)) = other.last_form
&& b < other.text.last_point().byte()
{
self.text.insert_tag(Ns::basic(), offset + b..end, id);
}
}
}
impl Default for Builder {
fn default() -> Self {
Builder {
text: Text::new(),
last_form: None,
last_mask: None,
buffer: String::with_capacity(50),
last_was_empty: false,
no_space_after_empty: false,
}
}
}
impl std::fmt::Debug for Builder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Builder")
.field("text", &self.text)
.finish_non_exhaustive()
}
}
impl From<Builder> for Text {
fn from(value: Builder) -> Self {
value.build()
}
}
#[derive(Clone)]
pub enum BuilderPart<'a, D: Display = String, _T = ()> {
Text(&'a Text),
Builder(&'a Builder),
ToString(&'a D),
Path(&'a std::path::Path),
PathKind(Text),
Form(FormTag),
Spacer(PhantomData<_T>),
Inlay(&'a Inlay),
Mask(Mask),
}
impl<'a, D: Display, _T> BuilderPart<'a, D, _T> {
fn try_to_basic(self) -> Result<BuilderPart<'a>, Self> {
match self {
Self::Text(text) => Ok(BuilderPart::Text(text)),
Self::Builder(builder) => Ok(BuilderPart::Builder(builder)),
Self::ToString(_) => Err(self),
Self::Path(path) => Ok(BuilderPart::Path(path)),
Self::PathKind(text) => Ok(BuilderPart::PathKind(text)),
Self::Form(form_id) => Ok(BuilderPart::Form(form_id)),
Self::Spacer(_) => Ok(BuilderPart::Spacer(PhantomData)),
Self::Inlay(ghost) => Ok(BuilderPart::Inlay(ghost)),
Self::Mask(mask) => Ok(BuilderPart::Mask(mask)),
}
}
}
pub trait AsBuilderPart<D: Display = String, _T = ()> {
fn as_builder_part(&self) -> BuilderPart<'_, D, _T>;
}
macro_rules! implAsBuilderPart {
($type:ident, $value:ident, $result:expr) => {
impl AsBuilderPart for $type {
fn as_builder_part(&self) -> BuilderPart<'_> {
let $value = self;
$result
}
}
};
}
implAsBuilderPart!(Builder, builder, BuilderPart::Builder(builder));
implAsBuilderPart!(FormId, form_id, BuilderPart::Form(form_id.to_tag(0)));
implAsBuilderPart!(FormTag, form_tag, BuilderPart::Form(*form_tag));
implAsBuilderPart!(Spacer, _spacer, BuilderPart::Spacer(PhantomData));
implAsBuilderPart!(Inlay, ghost, BuilderPart::Inlay(ghost));
implAsBuilderPart!(Text, text, BuilderPart::Text(text));
implAsBuilderPart!(Path, path, BuilderPart::Path(path));
implAsBuilderPart!(PathKind, path, BuilderPart::PathKind(path.name_txt()));
implAsBuilderPart!(Mask, mask, BuilderPart::Mask(*mask));
impl<D: Display> AsBuilderPart<D, D> for D {
fn as_builder_part(&self) -> BuilderPart<'_, D, D> {
BuilderPart::ToString(self)
}
}
#[macro_export]
macro_rules! txt {
($($parts:tt)+) => {{
#[allow(unused_imports)]
use $crate::{
__parse_arg__, __parse_form__, __parse_str__, private_exports::format_like
};
let mut builder = $crate::text::Builder::new();
let _ = format_like!(
__parse_str__,
[('{', __parse_arg__, false), ('[', __parse_form__, true)],
&mut builder,
$($parts)*
);
builder.build()
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __log__ {
($lvl:expr, $location:expr, $($arg:tt)*) => {{
#[allow(unused_must_use)]
let text = $crate::text::txt!($($arg)*);
$crate::context::logs().push_record($crate::context::Record::new(
text,
$lvl,
$location
));
}}
}
#[macro_export]
#[doc(hidden)]
macro_rules! __parse_str__ {
($builder:expr, $str:literal) => {{
let builder = $builder;
builder.push_str($str);
builder
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __parse_arg__ {
($builder:expr,"", $arg:expr) => {{
use $crate::text::AsBuilderPart;
let builder = $builder;
builder.push_builder_part($arg.as_builder_part());
builder
}};
($builder:expr, $modif:literal, $arg:expr) => {{
let builder = $builder;
builder.push_str(format!(concat!("{:", $modif, "}"), &$arg));
builder
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! __parse_form__ {
($builder:expr, $priority:literal,) => {{
use $crate::text::AsBuilderPart;
const PRIORITY: u8 = $crate::priority($priority);
let builder = $builder;
let id = $crate::form::DEFAULT_ID;
builder.push_builder_part(id.to_tag(PRIORITY).as_builder_part());
builder
}};
($builder:expr, $priority:literal, a) => {{
use $crate::text::AsBuilderPart;
const PRIORITY: u8 = $crate::priority($priority);
let builder = $builder;
let id = $crate::form::ACCENT_ID;
builder.push_builder_part(id.to_tag(PRIORITY).as_builder_part());
builder
}};
($builder:expr, $priority:literal, $($form:tt)*) => {{
use $crate::text::AsBuilderPart;
const PRIORITY: u8 = $crate::priority($priority);
let builder = $builder;
let id = $crate::form::id_of!(concat!($(stringify!($form)),*));
builder.push_builder_part(id.to_tag(PRIORITY).as_builder_part());
builder
}};
}