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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
use super::*;
use std::collections::BTreeMap;
use std::mem;

#[derive(Debug)]
pub struct Output {
    fmt: Formatting,

    /// Pending line
    line: String,

    /// Pending indent
    indent: usize,

    /// Written, completed lines
    lines: Vec<String>,

    /// Mapping from line number to comments associated with that line
    comments: BTreeMap<usize, Vec<String>>,

    /// Mapping from line number to indenting level present at that line
    indents: BTreeMap<usize, usize>,
}

impl Output {
    pub fn new(fmt: &Formatting) -> Self {
        Self {
            // Borrowing `fmt` makes the code awkward in a few places, so let's
            // just clone it
            fmt: fmt.to_owned(),
            line: Default::default(),
            indent: Default::default(),
            lines: Default::default(),
            comments: Default::default(),
            indents: Default::default(),
        }
    }

    pub fn write_key_and_separator(&mut self, key: impl ToString) {
        if self.fmt.objects_style.surround_keys_with_quotes {
            self.write_char('"');
            self.write(key);
            self.write_char('"');
        } else {
            self.write(key);
        }
        self.write_char(':');
        self.write_char(' ');
    }

    pub fn write_property_separator_ln(&mut self) {
        if self.fmt.objects_style.use_comma_as_separator {
            self.write_char(',');
        }
        self.ln();
    }

    pub fn write(&mut self, str: impl ToString) {
        for ch in str.to_string().chars() {
            self.write_char(ch);
        }
    }

    pub fn writeln(&mut self, str: impl ToString) {
        self.write(str);
        self.ln();
    }

    pub fn ln(&mut self) {
        self.write_char('\n');
    }

    pub fn writeln_comment(&mut self, comment: impl ToString) {
        let comment = comment.to_string();

        for comment in comment.split('\n') {
            let comment = if comment.contains('\t') {
                comment.replace('\t', &" ".repeat(self.fmt.indent_style.size))
            } else {
                comment.to_owned()
            };

            self.comments
                .entry(self.lines.len())
                .or_default()
                .push(comment);
        }
    }

    pub fn append_comment(&mut self, f: impl FnOnce(&mut String)) {
        let mut comment = self
            .comments
            .get_mut(&self.lines.len())
            .and_then(|comments| {
                if comments.len() == 1 {
                    comments.pop()
                } else {
                    None
                }
            })
            .unwrap_or_default();

        f(&mut comment);

        self.writeln_comment(comment);
    }

    pub fn inc_indent(&mut self) {
        self.indent += 1;
    }

    pub fn dec_indent(&mut self) {
        self.indent -= 1;
    }

    pub fn render(mut self) -> String {
        if !self.line.is_empty() {
            self.write_char('\n');
        }

        match self.fmt.layout {
            Layout::OneColumn => layouts::one_column::render(&self),
            Layout::TwoColumns { align, spacing } => {
                layouts::two_columns::render(&self, align, spacing)
            }
        }
    }

    fn write_char(&mut self, ch: char) {
        match ch {
            '\t' => {
                self.write(" ".repeat(self.fmt.indent_style.size));
            }

            '\r' => {
                //
            }

            '\n' => {
                self.lines.push(mem::take(&mut self.line));
            }

            ch => {
                if self.line.is_empty() {
                    self.indents.insert(self.lines.len(), self.indent);
                }

                self.line.push(ch);
            }
        }
    }
}

impl<'a> common::Output<'a> for Output {
    fn comment_separator(&self) -> &str {
        &self.fmt.comments_style.separator
    }

    fn lines(&'a self) -> Lines<'a> {
        Lines::new(
            &self.lines,
            self.fmt.indent_style.size,
            &self.indents,
            &self.comments,
        )
    }
}