1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
use serde::{Deserialize, Serialize};
use smart_default::SmartDefault;

use crate::Indent;

#[derive(Deserialize, Serialize, SmartDefault, Debug, Clone)]
/// Configuration for rsnfmt
#[serde(default)]
pub struct Config {
    /// Max line width
    #[default = 60]
    pub max_width: usize,
    /// Max level of inline nesting
    #[default = 2]
    pub max_inline_level: usize,
    /// Normalize all comments to a specific format
    pub normalize_comments: NormalizeComments,
    /// Wrap comments longer than `max_width`
    pub wrap_comments: bool,
    /// Should formatting preserve empty lines
    pub preserve_empty_lines: PreserveEmptyLines,
    /// Inherit parent/global configuration
    #[default = true]
    pub inherit: bool,
    /// Line ending
    pub line_ending: LineEnding,
    /// Indentation width
    #[default = 4]
    pub indent: usize,
    /// Use `\t` to indent
    pub hard_tab: bool,
}

impl Config {
    pub(crate) fn indent(&self) -> Indent {
        Indent {
            level: 0,
            hard_tab: self.hard_tab,
            width: self.indent,
        }
    }

    pub(crate) fn line_ending(&self, source: &str) -> &'static str {
        #[cfg(not(windows))]
        const PLATFORM: &str = "\n";
        #[cfg(windows)]
        const PLATFORM: &str = "\r\n";
        match self.line_ending {
            LineEnding::Detect => {
                if let Some(idx) = source.find('\n') {
                    if matches!(source.as_bytes().get(idx - 1), Some(b'\r')) {
                        "\r\n"
                    } else {
                        "\n"
                    }
                } else {
                    PLATFORM
                }
            }
            LineEnding::Platform => PLATFORM,
            LineEnding::Lf => "\n",
            LineEnding::CrLf => "\r\n",
        }
    }
}

#[derive(Deserialize, Serialize, Default, Debug, Clone, Copy)]
/// Should comments be normalized
pub enum NormalizeComments {
    /// Make all comments block comments (`/* */`)
    Block,
    /// Make all comments line comments (`//`)
    Line,
    /// Do not normalize Comments
    #[default]
    No,
}

#[derive(Deserialize, Serialize, Default, Debug, Clone, Copy)]
/// Should empty lines be preserved
pub enum PreserveEmptyLines {
    /// Reduce multiple empty lines to a single one
    One,
    /// Preserve all empty lines
    #[default]
    All,
    /// Do not preserve any empty lines
    None,
}

impl PreserveEmptyLines {
    pub(crate) fn is_none(self) -> bool {
        matches!(self, Self::None)
    }
}

#[derive(Deserialize, Serialize, Default, Debug, Clone, Copy)]
/// Line endings to use
pub enum LineEnding {
    /// Detect line endings from input
    ///
    /// Uses the first line ending encountered, if the file does not have any
    /// line breaks, falls back to [`Platform`](Self::Platform).
    #[default]
    Detect,
    /// Use platform line endings
    ///
    /// - `\n\r` on Windows
    /// - `\n` everywhere else
    Platform,
    /// Use unix line endings (`\n`)
    Lf,
    /// Use windows line endings (`\n\r`)
    CrLf,
}