use std::{cell::Cell, fmt};
pub(super) fn with_local_string<F, R>(f: F) -> R
where
F: FnOnce(&mut String) -> R,
{
thread_local! {
static GLOBAL_STR: Cell<Option<String>> = const { Cell::new(Some(String::new())) };
}
if let Some(mut s) = GLOBAL_STR.try_with(Cell::take).ok().flatten() {
let result = f(&mut s);
let _ = GLOBAL_STR.try_with(move |cell| {
s.clear();
cell.set(Some(s));
});
result
} else {
f(&mut String::new())
}
}
pub(super) trait StringLike {
fn push(&mut self, c: char);
fn push_str(&mut self, s: &str);
fn reserve(&mut self, additional: usize);
}
impl<T: StringLike> StringLike for &mut T {
fn push(&mut self, c: char) {
(**self).push(c);
}
fn push_str(&mut self, s: &str) {
(**self).push_str(s);
}
fn reserve(&mut self, additional: usize) {
(**self).reserve(additional);
}
}
impl StringLike for String {
fn push(&mut self, c: char) {
self.push(c);
}
fn push_str(&mut self, s: &str) {
self.push_str(s);
}
fn reserve(&mut self, additional: usize) {
self.reserve(additional);
}
}
#[non_exhaustive]
pub(super) struct Indented<T> {
pub output: T,
pub indent: usize,
}
impl<T> Indented<T> {
pub fn new(output: T, indent: usize) -> Self {
Self { output, indent }
}
}
impl<T: StringLike> StringLike for Indented<T> {
fn push(&mut self, c: char) {
if c == '\n' {
self.output.reserve(1 + self.indent);
self.output.push('\n');
for _ in 0..self.indent {
self.output.push(' ');
}
} else {
self.output.push(c);
}
}
fn push_str(&mut self, s: &str) {
let mut lines = s.split('\n');
if let Some(first_line) = lines.next() {
self.output.push_str(first_line);
for line in lines {
self.output.reserve(1 + self.indent + line.len());
self.output.push('\n');
for _ in 0..self.indent {
self.output.push(' ');
}
self.output.push_str(line);
}
}
}
fn reserve(&mut self, additional: usize) {
self.output.reserve(additional);
}
}
impl<T: StringLike> fmt::Write for Indented<T> {
fn write_char(&mut self, c: char) -> std::fmt::Result {
self.push(c);
Ok(())
}
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.push_str(s);
Ok(())
}
}