use std::fmt;
use std::ops::{Deref, DerefMut};
pub(super) type Indenter = CodeIndenter<String>;
#[macro_export]
macro_rules! indent_scope {
($x:ident) => {
let mut $x = $x.indented(1);
};
}
pub struct CodeIndenter<W: fmt::Write> {
writer: W,
level: u32,
needs_indenting: bool,
indent: &'static str,
}
impl<W: fmt::Write> CodeIndenter<W> {
pub fn new(writer: W, indent: &'static str) -> Self {
CodeIndenter {
writer,
level: 0,
needs_indenting: true,
indent,
}
}
pub fn into_inner(self) -> W {
self.writer
}
pub fn indent(&mut self, n: u32) {
self.level = self.level.saturating_add(n);
}
pub fn dedent(&mut self, n: u32) {
self.level = self.level.saturating_sub(n);
}
pub fn indented(&mut self, n: u32) -> IndentScope<'_, W> {
self.indent(n);
IndentScope { fmt: self }
}
fn write_indent(&mut self) -> fmt::Result {
for _ in 0..self.level {
self.writer.write_str(self.indent)?;
}
Ok(())
}
pub fn with_indent<Res>(&mut self, f: impl FnOnce(&mut Self) -> Res) -> Res {
let mut indenter = self.indented(1);
f(&mut indenter)
}
pub fn newline(&mut self) {
self.writer.write_char('\n').unwrap();
}
pub fn delimited_block<Res>(&mut self, before: &str, f: impl FnOnce(&mut Self) -> Res, after: &str) -> Res {
self.writer.write_str(before).unwrap();
let res = self.with_indent(|out| {
out.newline();
out.write_indent().unwrap();
f(out)
});
self.writer.write_str(after).unwrap();
res
}
}
impl<W: fmt::Write> fmt::Write for CodeIndenter<W> {
fn write_str(&mut self, s: &str) -> fmt::Result {
for (i, line) in s.split_inclusive('\n').enumerate() {
let write_indent = i != 0 || std::mem::take(&mut self.needs_indenting);
if write_indent && line != "\n" {
self.write_indent()?;
}
self.writer.write_str(line)?;
}
self.needs_indenting = s.ends_with('\n');
Ok(())
}
}
impl CodeIndenter<String> {
pub fn write_fmt(&mut self, args: fmt::Arguments) {
fmt::Write::write_fmt(self, args).unwrap()
}
}
pub struct IndentScope<'a, W: fmt::Write> {
fmt: &'a mut CodeIndenter<W>,
}
impl<W: fmt::Write> Drop for IndentScope<'_, W> {
fn drop(&mut self) {
self.fmt.dedent(1);
}
}
impl<T: fmt::Write> Deref for IndentScope<'_, T> {
type Target = CodeIndenter<T>;
fn deref(&self) -> &Self::Target {
self.fmt
}
}
impl<T: fmt::Write> DerefMut for IndentScope<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.fmt
}
}