use crate::{
config::{heuristics::WidthHeuristics, manifest::Config},
constants::{HARD_TAB, INDENT_BUFFER, INDENT_BUFFER_LEN},
FormatterError,
};
use std::{
borrow::Cow,
fmt::Write,
ops::{Add, Sub},
};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub(crate) struct Indent {
pub(crate) block_indent: usize,
}
impl Indent {
fn new(block_indent: usize) -> Self {
Self { block_indent }
}
fn empty() -> Self {
Self::new(0)
}
fn block_indent(&mut self, config: &Config) {
self.block_indent += config.whitespace.tab_spaces
}
fn block_unindent(&mut self, config: &Config) {
let tab_spaces = config.whitespace.tab_spaces;
if self.block_indent < tab_spaces {
} else {
self.block_indent -= tab_spaces
}
}
pub(crate) fn width(&self) -> usize {
self.block_indent
}
fn to_string_inner(
self,
config: &Config,
offset: usize,
) -> Result<Cow<'static, str>, FormatterError> {
let (num_tabs, num_spaces) = if config.whitespace.hard_tabs {
(self.block_indent / config.whitespace.tab_spaces, 0)
} else {
(0, self.width())
};
let num_chars = num_tabs + num_spaces;
if num_tabs == 0 && num_chars + offset <= INDENT_BUFFER_LEN {
Ok(Cow::from(&INDENT_BUFFER[offset..=num_chars]))
} else {
let mut indent = String::with_capacity(num_chars + usize::from(offset == 0));
if offset == 0 {
writeln!(indent)?;
}
for _ in 0..num_tabs {
write!(indent, "{HARD_TAB}")?;
}
for _ in 0..num_spaces {
write!(indent, " ")?;
}
Ok(Cow::from(indent))
}
}
pub(crate) fn to_string(self, config: &Config) -> Result<Cow<'static, str>, FormatterError> {
self.to_string_inner(config, 1)
}
pub(crate) fn to_string_with_newline(
self,
config: &Config,
) -> Result<Cow<'static, str>, FormatterError> {
self.to_string_inner(config, 0)
}
}
impl Add for Indent {
type Output = Indent;
fn add(self, rhs: Indent) -> Indent {
Indent {
block_indent: self.block_indent + rhs.block_indent,
}
}
}
impl Sub for Indent {
type Output = Indent;
fn sub(self, rhs: Indent) -> Indent {
Indent::new(self.block_indent - rhs.block_indent)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub(crate) struct CodeLine {
pub(crate) width: usize,
pub(crate) line_style: LineStyle,
pub(crate) expr_kind: ExprKind,
pub(crate) has_where_clause: bool,
}
impl CodeLine {
pub(crate) fn from(line_style: LineStyle, expr_kind: ExprKind) -> Self {
Self {
width: Default::default(),
line_style,
expr_kind,
has_where_clause: Default::default(),
}
}
pub(crate) fn reset_width(&mut self) {
self.width = 0;
}
pub(crate) fn update_width(&mut self, new_width: usize) {
self.width = new_width;
}
pub(crate) fn add_width(&mut self, extra_width: usize) {
self.width += extra_width;
}
pub(crate) fn sub_width(&mut self, extra_width: usize) {
self.width -= extra_width;
}
pub(crate) fn update_line_style(&mut self, line_style: LineStyle) {
self.line_style = line_style;
}
pub(crate) fn update_expr_kind(&mut self, expr_kind: ExprKind) {
self.expr_kind = expr_kind;
}
pub(crate) fn update_where_clause(&mut self, has_where_clause: bool) {
self.has_where_clause = has_where_clause;
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum LineStyle {
Normal,
Inline,
Multiline,
}
impl Default for LineStyle {
fn default() -> Self {
Self::Normal
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum ExprKind {
Variable,
Function,
Struct,
Collection,
MethodChain,
Conditional,
Import,
Undetermined,
}
impl Default for ExprKind {
fn default() -> Self {
Self::Undetermined
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub struct Shape {
pub(crate) indent: Indent,
pub(crate) code_line: CodeLine,
pub(crate) width_heuristics: WidthHeuristics,
}
impl Shape {
pub(crate) fn apply_width_heuristics(&mut self, width_heuristics: WidthHeuristics) {
if width_heuristics != WidthHeuristics::default() {
self.width_heuristics = width_heuristics
}
}
pub(crate) fn block_indent(&mut self, config: &Config) {
self.indent.block_indent(config)
}
pub(crate) fn block_unindent(&mut self, config: &Config) {
self.indent.block_unindent(config)
}
pub(crate) fn get_line_style(
&mut self,
field_width: Option<usize>,
body_width: Option<usize>,
config: &Config,
) {
match self.code_line.expr_kind {
ExprKind::Struct => {
if config.structures.small_structures_single_line {
if self.code_line.width > config.whitespace.max_width
|| field_width.unwrap_or(0) > self.width_heuristics.structure_field_width
|| body_width.unwrap_or(0) > self.width_heuristics.structure_lit_width
{
self.code_line.update_line_style(LineStyle::Multiline)
} else {
self.code_line.update_line_style(LineStyle::Inline)
}
} else {
self.code_line.update_line_style(LineStyle::Multiline)
}
}
ExprKind::Collection => {
if self.code_line.width > config.whitespace.max_width
|| body_width.unwrap_or(0) > self.width_heuristics.collection_width
{
self.code_line.update_line_style(LineStyle::Multiline)
} else {
self.code_line.update_line_style(LineStyle::Normal)
}
}
ExprKind::Import => {
if self.code_line.width > config.whitespace.max_width {
self.code_line.update_line_style(LineStyle::Multiline)
} else {
self.code_line.update_line_style(LineStyle::Normal)
}
}
ExprKind::Function => {
if self.code_line.width > config.whitespace.max_width
|| body_width.unwrap_or(0) > self.width_heuristics.fn_call_width
{
self.code_line.update_line_style(LineStyle::Multiline)
} else {
self.code_line.update_line_style(LineStyle::Normal)
}
}
ExprKind::Conditional => {
if self.code_line.width < self.width_heuristics.single_line_if_else_max_width {
self.code_line.update_line_style(LineStyle::Inline)
} else if body_width.unwrap_or(0) > self.width_heuristics.chain_width {
self.code_line.update_line_style(LineStyle::Multiline)
} else {
self.code_line.update_line_style(LineStyle::Normal)
}
}
_ => self.code_line.update_line_style(LineStyle::default()),
}
}
pub(crate) fn with_code_line_from(self, line_style: LineStyle, expr_kind: ExprKind) -> Self {
Self {
code_line: CodeLine::from(line_style, expr_kind),
..self
}
}
pub(crate) fn with_default_code_line(self) -> Self {
Self {
code_line: CodeLine::default(),
..self
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::formatter::Formatter;
#[test]
fn indent_add_sub() {
let indent = Indent::new(4) + Indent::new(8);
assert_eq!(12, indent.block_indent);
let indent = indent - Indent::new(4);
assert_eq!(8, indent.block_indent);
}
#[test]
fn indent_to_string_spaces() {
let mut formatter = Formatter::default();
formatter.shape.indent = Indent::new(12);
assert_eq!(
" ",
formatter.shape.indent.to_string(&formatter.config).unwrap()
);
}
#[test]
fn indent_to_string_hard_tabs() {
let mut formatter = Formatter::default();
formatter.config.whitespace.hard_tabs = true;
formatter.shape.indent = Indent::new(8);
assert_eq!(
"\t\t",
formatter.shape.indent.to_string(&formatter.config).unwrap()
);
}
#[test]
fn shape_block_indent() {
let mut formatter = Formatter::default();
formatter.config.whitespace.tab_spaces = 24;
let max_width = formatter.config.whitespace.max_width;
formatter.shape.code_line.width = max_width;
formatter.indent();
assert_eq!(max_width, formatter.shape.code_line.width);
assert_eq!(24, formatter.shape.indent.block_indent);
}
#[test]
fn test_get_line_style_struct() {
let mut formatter = Formatter::default();
formatter.shape.code_line.update_expr_kind(ExprKind::Struct);
formatter
.shape
.get_line_style(Some(9), Some(18), &formatter.config);
assert_eq!(LineStyle::Inline, formatter.shape.code_line.line_style);
formatter
.shape
.get_line_style(Some(10), Some(19), &formatter.config);
assert_eq!(LineStyle::Multiline, formatter.shape.code_line.line_style);
}
}